Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
}
18 changes: 15 additions & 3 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,24 @@

echo "🔍 Running pre-commit checks..."

# Lint and format staged files
echo "📝 Linting and formatting..."
pnpm lint-staged
# Build packages first (needed for typecheck due to workspace dependencies)
echo "🔨 Building packages..."
pnpm build > /dev/null 2>&1

# Get staged files
files=$(git diff --cached --name-only --diff-filter=ACMR "*.ts" "*.tsx" "*.js" "*.jsx" | xargs)

if [ -n "$files" ]; then
echo "📝 Linting and formatting staged files..."
pnpm lint-staged $files
git add $files
fi

# Type check entire project
echo "🔎 Type checking..."
pnpm typecheck

# Clean up dist files (they shouldn't be committed)
rm -rf packages/*/dist

echo "✅ Pre-commit checks passed!"
6 changes: 4 additions & 2 deletions biome.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.0/schema.json",
"$schema": "https://biomejs.dev/schemas/2.3.10/schema.json",
"assist": { "actions": { "source": { "organizeImports": "on" } } },
"linter": {
"enabled": true,
Expand Down Expand Up @@ -30,7 +30,9 @@
{
"includes": [
"packages/core/src/types.ts",
"packages/core/src/runtime.ts"
"packages/core/src/runtime.ts",
"packages/plugins/src/types.ts",
"specs/**/contracts/types.ts"
],
"linter": {
"rules": {
Expand Down
8 changes: 2 additions & 6 deletions commitlint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'body-max-line-length': [2, 'always', 100],
'subject-case': [
2,
'never',
['start-case', 'pascal-case', 'upper-case'],
],
'subject-case': [2, 'never', ['start-case', 'pascal-case', 'upper-case']],
},
};
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,4 @@
"dependencies": {
"@lytics/sdk-kit-plugins": "0.1.2"
}
}
}
6 changes: 4 additions & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,14 @@
"test:watch": "vitest"
},
"dependencies": {
"@lytics/sdk-kit": "^0.1.1"
"@lytics/sdk-kit": "^0.1.1",
"@lytics/sdk-kit-plugins": "^0.1.2",
"@prosdevlab/experience-sdk-plugins": "workspace:*"
},
"devDependencies": {
"@types/node": "^24.0.0",
"tsup": "^8.5.1",
"typescript": "^5.9.3",
"vitest": "^4.0.16"
}
}
}
53 changes: 26 additions & 27 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,45 @@
* built on @lytics/sdk-kit.
*/

// Export all types
export type {
Experience,
TargetingRules,
UrlRule,
FrequencyRule,
FrequencyConfig,
ExperienceContent,
BannerContent,
ModalContent,
TooltipContent,
ModalAction,
Context,
UserContext,
Decision,
TraceStep,
DecisionMetadata,
ExperienceConfig,
RuntimeState,
} from './types';
// Re-export plugins for convenience
export { bannerPlugin, debugPlugin, frequencyPlugin } from '@prosdevlab/experience-sdk-plugins';

// Export runtime class and functions
export {
ExperienceRuntime,
buildContext,
ExperienceRuntime,
evaluateExperience,
evaluateUrlRule,
} from './runtime';

// Export singleton API
export {
createInstance,
init,
register,
destroy,
evaluate,
explain,
getState,
init,
on,
destroy,
experiences as default,
register,
} from './singleton';


// Export all types
export type {
BannerContent,
Context,
Decision,
DecisionMetadata,
Experience,
ExperienceConfig,
ExperienceContent,
FrequencyConfig,
FrequencyRule,
ModalAction,
ModalContent,
RuntimeState,
TargetingRules,
TooltipContent,
TraceStep,
UrlRule,
UserContext,
} from './types';
95 changes: 45 additions & 50 deletions packages/core/src/runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,17 +93,17 @@ describe('ExperienceRuntime', () => {
});

it('should allow multiple experiences', () => {
runtime.register('exp1', {
type: 'banner',
targeting: {},
content: { title: 'Exp 1', message: 'Message 1' },
});
runtime.register('exp1', {
type: 'banner',
targeting: {},
content: { title: 'Exp 1', message: 'Message 1' },
});

runtime.register('exp2', {
type: 'banner',
targeting: {},
content: { title: 'Exp 2', message: 'Message 2' },
});
runtime.register('exp2', {
type: 'banner',
targeting: {},
content: { title: 'Exp 2', message: 'Message 2' },
});

const state = runtime.getState();
expect(state.experiences.size).toBe(2);
Expand All @@ -112,13 +112,13 @@ describe('ExperienceRuntime', () => {

describe('evaluate()', () => {
beforeEach(() => {
runtime.register('test', {
type: 'banner',
targeting: {
url: { contains: '/products' },
},
content: { title: 'Test', message: 'Test message' },
});
runtime.register('test', {
type: 'banner',
targeting: {
url: { contains: '/products' },
},
content: { title: 'Test', message: 'Test message' },
});
});

it('should return decision with matched experience', () => {
Expand Down Expand Up @@ -199,13 +199,13 @@ describe('ExperienceRuntime', () => {
});

it('should match first experience only', () => {
runtime.register('test2', {
type: 'banner',
targeting: {
url: { contains: '/products' },
},
content: { title: 'Test 2', message: 'Test 2 message' },
});
runtime.register('test2', {
type: 'banner',
targeting: {
url: { contains: '/products' },
},
content: { title: 'Test 2', message: 'Test 2 message' },
});

const decision = runtime.evaluate({
url: 'https://example.com/products',
Expand All @@ -218,13 +218,13 @@ describe('ExperienceRuntime', () => {

describe('explain()', () => {
it('should explain specific experience', () => {
runtime.register('test', {
type: 'banner',
targeting: {
url: { contains: '/test' },
},
content: { title: 'Test', message: 'Test message' },
});
runtime.register('test', {
type: 'banner',
targeting: {
url: { contains: '/test' },
},
content: { title: 'Test', message: 'Test message' },
});

const explanation = runtime.explain('test');

Expand Down Expand Up @@ -286,12 +286,12 @@ describe('ExperienceRuntime', () => {

describe('destroy()', () => {
it('should clean up runtime', async () => {
await runtime.init();
runtime.register('test', {
type: 'banner',
targeting: {},
content: { title: 'Test', message: 'Test message' },
});
await runtime.init();
runtime.register('test', {
type: 'banner',
targeting: {},
content: { title: 'Test', message: 'Test message' },
});

await runtime.destroy();

Expand Down Expand Up @@ -398,26 +398,22 @@ describe('ExperienceRuntime', () => {

describe('evaluateUrlRule', () => {
it('should match with equals rule', () => {
expect(evaluateUrlRule({ equals: 'https://example.com' }, 'https://example.com')).toBe(
true
);
expect(evaluateUrlRule({ equals: 'https://example.com' }, 'https://example.com')).toBe(true);
expect(evaluateUrlRule({ equals: 'https://example.com' }, 'https://other.com')).toBe(false);
});

it('should match with contains rule', () => {
expect(evaluateUrlRule({ contains: '/products' }, 'https://example.com/products')).toBe(
true
);
expect(evaluateUrlRule({ contains: '/products' }, 'https://example.com/products')).toBe(true);
expect(evaluateUrlRule({ contains: '/products' }, 'https://example.com/about')).toBe(false);
});

it('should match with regex rule', () => {
expect(evaluateUrlRule({ matches: /\/product\/\d+/ }, 'https://example.com/product/123')).toBe(
true
);
expect(evaluateUrlRule({ matches: /\/product\/\d+/ }, 'https://example.com/product/abc')).toBe(
false
);
expect(
evaluateUrlRule({ matches: /\/product\/\d+/ }, 'https://example.com/product/123')
).toBe(true);
expect(
evaluateUrlRule({ matches: /\/product\/\d+/ }, 'https://example.com/product/abc')
).toBe(false);
});

it('should return true for empty rule', () => {
Expand Down Expand Up @@ -465,4 +461,3 @@ describe('ExperienceRuntime', () => {
});
});
});

9 changes: 8 additions & 1 deletion packages/core/src/runtime.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { SDK } from '@lytics/sdk-kit';
import { storagePlugin } from '@lytics/sdk-kit-plugins';
import { bannerPlugin, debugPlugin, frequencyPlugin } from '@prosdevlab/experience-sdk-plugins';
import type {
Context,
Decision,
Expand Down Expand Up @@ -32,6 +34,12 @@ export class ExperienceRuntime {
name: 'experience-sdk',
...config,
});

// Auto-register plugins
this.sdk.use(storagePlugin);
this.sdk.use(debugPlugin);
this.sdk.use(frequencyPlugin);
this.sdk.use(bannerPlugin);
}

/**
Expand Down Expand Up @@ -263,4 +271,3 @@ export function evaluateUrlRule(rule: UrlRule, url: string = ''): boolean {
// No rules specified = match all
return true;
}

9 changes: 4 additions & 5 deletions packages/core/src/singleton.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { beforeEach, describe, expect, it } from 'vitest';
import {
createInstance,
init,
register,
destroy,
evaluate,
experiences as experiencesDefault,
explain,
getState,
init,
on,
destroy,
experiences as experiencesDefault,
register,
} from './singleton';

describe('Export Pattern', () => {
Expand Down Expand Up @@ -234,4 +234,3 @@ describe('Export Pattern', () => {
});
});
});

3 changes: 1 addition & 2 deletions packages/core/src/singleton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import { ExperienceRuntime } from './runtime';
import type { ExperienceConfig, Experience, Context, Decision, RuntimeState } from './types';
import type { Context, Decision, Experience, ExperienceConfig, RuntimeState } from './types';

/**
* Create a new Experience SDK instance
Expand Down Expand Up @@ -174,4 +174,3 @@ export const experiences = {
if (typeof window !== 'undefined') {
(window as unknown as Record<string, unknown>).experiences = experiences;
}

1 change: 0 additions & 1 deletion packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,3 @@ export interface RuntimeState {
/** Current configuration */
config: ExperienceConfig;
}

2 changes: 1 addition & 1 deletion packages/core/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}
}
4 changes: 2 additions & 2 deletions packages/core/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ export default defineConfig({
minify: true,
outDir: 'dist',
globalName: 'experiences',
// Bundle sdk-kit for IIFE (script tag)
noExternal: ['@lytics/sdk-kit'],
// Bundle dependencies for IIFE (script tag)
noExternal: ['@lytics/sdk-kit', '@lytics/sdk-kit-plugins', '@prosdevlab/experience-sdk-plugins'],
});
Loading