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
57 changes: 55 additions & 2 deletions content/docs/references/api/auth-endpoints.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ the canonical API contract.
## TypeScript Usage

```typescript
import { AuthEndpoint } from '@objectstack/spec/api';
import type { AuthEndpoint } from '@objectstack/spec/api';
import { AuthEndpoint, AuthFeaturesConfig, AuthProviderInfo, EmailPasswordConfigPublic, GetAuthConfigResponse } from '@objectstack/spec/api';
import type { AuthEndpoint, AuthFeaturesConfig, AuthProviderInfo, EmailPasswordConfigPublic, GetAuthConfigResponse } from '@objectstack/spec/api';

// Validate data
const result = AuthEndpoint.parse(data);
Expand All @@ -51,3 +51,56 @@ const result = AuthEndpoint.parse(data);

---

## AuthFeaturesConfig

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **twoFactor** | `boolean` | ✅ | Two-factor authentication enabled |
| **passkeys** | `boolean` | ✅ | Passkey/WebAuthn support enabled |
| **magicLink** | `boolean` | ✅ | Magic link login enabled |
| **organization** | `boolean` | ✅ | Multi-tenant organization support enabled |


---

## AuthProviderInfo

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **id** | `string` | ✅ | Provider ID (e.g., google, github, microsoft) |
| **name** | `string` | ✅ | Display name (e.g., Google, GitHub) |
| **enabled** | `boolean` | ✅ | Whether this provider is enabled |


---

## EmailPasswordConfigPublic

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **enabled** | `boolean` | ✅ | Whether email/password auth is enabled |
| **disableSignUp** | `boolean` | optional | Whether new user registration is disabled |
| **requireEmailVerification** | `boolean` | optional | Whether email verification is required |


---

## GetAuthConfigResponse

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **emailPassword** | `Object` | ✅ | Email/password authentication config |
| **socialProviders** | `Object[]` | ✅ | Available social/OAuth providers |
| **features** | `Object` | ✅ | Enabled authentication features |


---

1 change: 1 addition & 0 deletions content/docs/references/kernel/manifest.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const result = Manifest.parse(data);
| :--- | :--- | :--- | :--- |
| **id** | `string` | ✅ | Unique package identifier (reverse domain style) |
| **namespace** | `string` | optional | Short namespace identifier for metadata scoping (e.g. "crm", "todo") |
| **defaultDatasource** | `string` | ✅ | Default datasource for all objects in this package |
| **version** | `string` | ✅ | Package version (semantic versioning) |
| **type** | `Enum<'plugin' \| 'ui' \| 'driver' \| 'server' \| 'app' \| 'theme' \| 'agent' \| 'objectql' \| 'module' \| 'gateway' \| 'adapter'>` | ✅ | Type of package |
| **name** | `string` | ✅ | Human-readable package name |
Expand Down
12 changes: 6 additions & 6 deletions packages/objectql/src/datasource-mapping.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ describe('DatasourceMapping', () => {

beforeEach(() => {
engine = new ObjectQL();
SchemaRegistry.clear();
SchemaRegistry.reset();
});

it('should route objects by namespace', async () => {
Expand All @@ -62,7 +62,7 @@ describe('DatasourceMapping', () => {
);

// Test that it uses memory driver
const result = await engine.create('account', { name: 'Test Account' });
const result = await engine.insert('account', { name: 'Test Account' });
expect(result).toBeDefined();
Comment on lines 62 to 66
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These datasource-mapping tests don’t currently verify which driver was selected (both mock drivers return the same shape), so the assertions will pass even if routing is broken. Consider asserting on memoryDriver.create/tursoDriver.create call counts (and ensure the registered object definition includes whatever fields the routing logic matches on, e.g. namespace, so the namespace rule can actually trigger).

Copilot uses AI. Check for mistakes.
expect(result.name).toBe('Test Account');
});
Expand Down Expand Up @@ -91,7 +91,7 @@ describe('DatasourceMapping', () => {
'own'
);

const result = await engine.create('sys_user', { username: 'admin' });
const result = await engine.insert('sys_user', { username: 'admin' });
expect(result).toBeDefined();
});

Expand Down Expand Up @@ -119,7 +119,7 @@ describe('DatasourceMapping', () => {
);

// Should use turso (priority 50) not memory (priority 100)
const result = await engine.create('account', { name: 'Test' });
const result = await engine.insert('account', { name: 'Test' });
expect(result).toBeDefined();
});

Expand Down Expand Up @@ -147,7 +147,7 @@ describe('DatasourceMapping', () => {
);

// Should use memory (default)
const result = await engine.create('task', { title: 'Do something' });
const result = await engine.insert('task', { title: 'Do something' });
expect(result).toBeDefined();
});

Expand Down Expand Up @@ -175,7 +175,7 @@ describe('DatasourceMapping', () => {
);

// Should use turso (explicit) not memory (mapping)
const result = await engine.create('account', { name: 'Test' });
const result = await engine.insert('account', { name: 'Test' });
expect(result).toBeDefined();
});
});
16 changes: 15 additions & 1 deletion packages/objectql/src/engine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,26 @@ import type { IDataDriver } from '@objectstack/spec/contracts';
// Mock the SchemaRegistry to avoid side effects between tests
vi.mock('./registry', () => {
const mockObjects = new Map();
const mockContributors = new Map();
return {
SchemaRegistry: {
getObject: vi.fn((name) => mockObjects.get(name)),
resolveObject: vi.fn((name) => mockObjects.get(name)),
registerObject: vi.fn((obj, packageId, namespace, ownership, priority) => {
const fqn = namespace ? `${namespace}__${obj.name}` : obj.name;
mockObjects.set(fqn, { ...obj, name: fqn });
// Also track contributors for getObjectOwner
if (!mockContributors.has(fqn)) {
mockContributors.set(fqn, []);
}
const contributors = mockContributors.get(fqn);
contributors.push({ packageId, namespace, ownership, priority, definition: obj });
return fqn;
}),
getObjectOwner: vi.fn((fqn) => {
const contributors = mockContributors.get(fqn);
return contributors?.find(c => c.ownership === 'own');
}),
registerNamespace: vi.fn(),
registerKind: vi.fn(),
registerItem: vi.fn(),
Expand All @@ -25,7 +36,10 @@ vi.mock('./registry', () => {
enabled: true,
installedAt: new Date().toISOString(),
})),
reset: vi.fn(() => mockObjects.clear()),
reset: vi.fn(() => {
mockObjects.clear();
mockContributors.clear();
}),
metadata: {
get: vi.fn(() => mockObjects) // Expose for verification if needed
}
Expand Down
4 changes: 3 additions & 1 deletion packages/objectql/src/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,9 @@ export class ObjectQL implements IDataEngine {
}

// 3. Check package's defaultDatasource
const owner = SchemaRegistry.getObjectOwner(objectName);
// Use the object's FQN name (from getObject) for ownership lookup
const fqn = object?.name || objectName;
const owner = SchemaRegistry.getObjectOwner(fqn);
if (owner?.packageId) {
const manifest = this.manifests.get(owner.packageId);
if (manifest?.defaultDatasource && manifest.defaultDatasource !== 'default') {
Expand Down
6 changes: 5 additions & 1 deletion packages/spec/src/stack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,8 +430,12 @@ describe('defineStack', () => {
const result = defineStack(config);
// Default is now strict=true, so result is validated and is a different object reference
expect(result).not.toBe(config); // Validation creates new object
expect(result).toEqual(config); // But content is the same
// Validation may add defaults like defaultDatasource
expect(result.manifest).toBeDefined();
expect(result.manifest.id).toBe(baseManifest.id);
expect(result.manifest.name).toBe(baseManifest.name);
expect(result.manifest.version).toBe(baseManifest.version);
expect(result.manifest.type).toBe(baseManifest.type);
Comment on lines +433 to +438
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test no longer asserts the key behavior that changed (the defaulting of manifest.defaultDatasource). Since ManifestSchema sets defaultDatasource via .default('default'), it would be better to explicitly expect result.manifest.defaultDatasource to be 'default' (and optionally keep a stronger structural assertion like toMatchObject(config) for the rest) so the test still guards against unintended validation transforms.

Suggested change
// Validation may add defaults like defaultDatasource
expect(result.manifest).toBeDefined();
expect(result.manifest.id).toBe(baseManifest.id);
expect(result.manifest.name).toBe(baseManifest.name);
expect(result.manifest.version).toBe(baseManifest.version);
expect(result.manifest.type).toBe(baseManifest.type);
// Validation preserves the provided structure while applying schema defaults
expect(result).toMatchObject(config);
expect(result.manifest).toBeDefined();
expect(result.manifest.defaultDatasource).toBe('default');

Copilot uses AI. Check for mistakes.
});

it('should return config as-is when strict is false', () => {
Expand Down
Loading