@haverstack/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +124 -0
- package/dist/id.d.ts +36 -0
- package/dist/id.d.ts.map +1 -0
- package/dist/id.js +129 -0
- package/dist/id.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/schema.d.ts +44 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +100 -0
- package/dist/schema.js.map +1 -0
- package/dist/stack.d.ts +187 -0
- package/dist/stack.d.ts.map +1 -0
- package/dist/stack.js +429 -0
- package/dist/stack.js.map +1 -0
- package/dist/types.d.ts +216 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/dist/validate.d.ts +28 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +92 -0
- package/dist/validate.js.map +1 -0
- package/package.json +42 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stack — Core Type Definitions
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* This file is the source of truth for all types in the
|
|
5
|
+
* Stack library. No logic lives here — just shapes.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Crockford base-32 encoded ID — time-sortable, unique within a stack.
|
|
9
|
+
* Format: 9-char timestamp prefix + 3-char random suffix = 12 chars total.
|
|
10
|
+
* Human-readable and URL-safe. Uniqueness is within-stack only;
|
|
11
|
+
* cross-stack references must include a stackUrl to disambiguate.
|
|
12
|
+
*/
|
|
13
|
+
export type RecordId = string;
|
|
14
|
+
/** Namespaced, versioned type identifier e.g. "com.example.myapp/note@2" */
|
|
15
|
+
export type TypeId = string;
|
|
16
|
+
/** Opaque file identifier returned by putAttachment */
|
|
17
|
+
export type FileId = string;
|
|
18
|
+
export type TagAssociation = {
|
|
19
|
+
kind: 'tag';
|
|
20
|
+
label: string;
|
|
21
|
+
};
|
|
22
|
+
export type AttachmentAssociation = {
|
|
23
|
+
kind: 'attachment';
|
|
24
|
+
label: string;
|
|
25
|
+
fileId: FileId;
|
|
26
|
+
mimeType: string;
|
|
27
|
+
};
|
|
28
|
+
export type RelationshipAssociation = {
|
|
29
|
+
kind: 'relationship';
|
|
30
|
+
label: string;
|
|
31
|
+
recordId: RecordId;
|
|
32
|
+
};
|
|
33
|
+
export type Association = TagAssociation | AttachmentAssociation | RelationshipAssociation;
|
|
34
|
+
/**
|
|
35
|
+
* Grants of access to a Record. Absence of permissions (empty or undefined)
|
|
36
|
+
* means private — readable only by the stack owner. Permissions are
|
|
37
|
+
* declarative intent; enforcement is the API adapter's responsibility.
|
|
38
|
+
*/
|
|
39
|
+
export type Permission = {
|
|
40
|
+
access: 'public';
|
|
41
|
+
} | {
|
|
42
|
+
access: 'entity';
|
|
43
|
+
entityId: RecordId;
|
|
44
|
+
read: boolean;
|
|
45
|
+
write: boolean;
|
|
46
|
+
} | {
|
|
47
|
+
access: 'group';
|
|
48
|
+
groupId: RecordId;
|
|
49
|
+
read: boolean;
|
|
50
|
+
write: boolean;
|
|
51
|
+
};
|
|
52
|
+
export type StackRecord = {
|
|
53
|
+
id: RecordId;
|
|
54
|
+
typeId: TypeId;
|
|
55
|
+
createdAt: Date;
|
|
56
|
+
updatedAt: Date;
|
|
57
|
+
content: Record<string, unknown>;
|
|
58
|
+
version: number;
|
|
59
|
+
parentId?: RecordId;
|
|
60
|
+
entityId?: RecordId;
|
|
61
|
+
appId?: RecordId;
|
|
62
|
+
deletedAt?: Date;
|
|
63
|
+
permissions?: Permission[];
|
|
64
|
+
associations?: Association[];
|
|
65
|
+
};
|
|
66
|
+
export type RecordVersion = {
|
|
67
|
+
version: number;
|
|
68
|
+
content: Record<string, unknown>;
|
|
69
|
+
updatedAt: Date;
|
|
70
|
+
entityId?: RecordId;
|
|
71
|
+
};
|
|
72
|
+
export type ScalarFieldKind = 'string' | 'number' | 'boolean' | 'date' | 'text' | 'record-ref';
|
|
73
|
+
export type ScalarFieldDef = {
|
|
74
|
+
kind: ScalarFieldKind;
|
|
75
|
+
required?: boolean;
|
|
76
|
+
};
|
|
77
|
+
export type ArrayFieldDef = {
|
|
78
|
+
kind: 'array';
|
|
79
|
+
items: FieldDef;
|
|
80
|
+
required?: boolean;
|
|
81
|
+
};
|
|
82
|
+
export type ObjectFieldDef = {
|
|
83
|
+
kind: 'object';
|
|
84
|
+
properties: TypeSchema;
|
|
85
|
+
required?: boolean;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* A field definition in a Type schema. Supports scalars, arrays, and nested
|
|
89
|
+
* objects. Arrays and nested objects are schema-validated on write but are
|
|
90
|
+
* opaque to the query engine in v1 — only top-level scalar fields support
|
|
91
|
+
* exact-match content filtering in queries.
|
|
92
|
+
*/
|
|
93
|
+
export type FieldDef = ScalarFieldDef | ArrayFieldDef | ObjectFieldDef;
|
|
94
|
+
export type TypeSchema = {
|
|
95
|
+
[fieldName: string]: FieldDef;
|
|
96
|
+
};
|
|
97
|
+
export type StackType = {
|
|
98
|
+
id: TypeId;
|
|
99
|
+
baseId: string;
|
|
100
|
+
version: number;
|
|
101
|
+
name: string;
|
|
102
|
+
schema: TypeSchema;
|
|
103
|
+
schemaHash: string;
|
|
104
|
+
migratesFrom?: TypeId;
|
|
105
|
+
createdAt: Date;
|
|
106
|
+
};
|
|
107
|
+
/** Content for _entity records */
|
|
108
|
+
export type EntityContent = {
|
|
109
|
+
/** Display name — human-friendly, not necessarily unique. May contain spaces and punctuation. e.g. "Jane Smith" */
|
|
110
|
+
name: string;
|
|
111
|
+
/** Short unique identifier within a namespace — URL-safe, no spaces. e.g. "janesmith". Like a username. Optional for private entities. */
|
|
112
|
+
handle?: string;
|
|
113
|
+
};
|
|
114
|
+
/** Content for _app records */
|
|
115
|
+
export type AppContent = {
|
|
116
|
+
/** Display name of the app e.g. "My Notes App" */
|
|
117
|
+
name: string;
|
|
118
|
+
/**
|
|
119
|
+
* Semver string e.g. "1.0.0". The app's unique machine-readable identity
|
|
120
|
+
* is already captured by the _app record's appId (e.g. "com.example.myapp"),
|
|
121
|
+
* so no handle is needed here.
|
|
122
|
+
*/
|
|
123
|
+
version?: string;
|
|
124
|
+
};
|
|
125
|
+
/** Content for _group records */
|
|
126
|
+
export type GroupContent = {
|
|
127
|
+
/** Display name — human-friendly, not necessarily unique. May contain spaces and punctuation. e.g. "Jane's Book Club" */
|
|
128
|
+
name: string;
|
|
129
|
+
/** Short unique identifier — URL-safe, no spaces. e.g. "janes-book-club". Useful for groups other people need to reference. Optional for private groups. */
|
|
130
|
+
handle?: string;
|
|
131
|
+
/** If present, this group owns a shared collaborative stack at this URL. Absent = permission-only group. */
|
|
132
|
+
stackUrl?: string;
|
|
133
|
+
};
|
|
134
|
+
/** Reserved system type IDs */
|
|
135
|
+
export declare const SYSTEM_TYPES: {
|
|
136
|
+
readonly ENTITY: "_entity";
|
|
137
|
+
readonly APP: "_app";
|
|
138
|
+
readonly GROUP: "_group";
|
|
139
|
+
};
|
|
140
|
+
export type DateRange = {
|
|
141
|
+
before?: Date;
|
|
142
|
+
after?: Date;
|
|
143
|
+
};
|
|
144
|
+
export type RecordFilter = {
|
|
145
|
+
typeId?: TypeId | TypeId[];
|
|
146
|
+
parentId?: RecordId | null;
|
|
147
|
+
appId?: RecordId | RecordId[];
|
|
148
|
+
entityId?: RecordId | RecordId[];
|
|
149
|
+
createdAt?: DateRange;
|
|
150
|
+
updatedAt?: DateRange;
|
|
151
|
+
tags?: string[];
|
|
152
|
+
hasAttachment?: string;
|
|
153
|
+
relatedTo?: {
|
|
154
|
+
recordId: RecordId;
|
|
155
|
+
label?: string;
|
|
156
|
+
};
|
|
157
|
+
content?: Record<string, unknown>;
|
|
158
|
+
search?: string;
|
|
159
|
+
includeDeleted?: boolean;
|
|
160
|
+
};
|
|
161
|
+
export type QuerySort = {
|
|
162
|
+
field: 'createdAt' | 'updatedAt' | 'version';
|
|
163
|
+
direction?: 'asc' | 'desc';
|
|
164
|
+
};
|
|
165
|
+
export type StackQuery = {
|
|
166
|
+
filter?: RecordFilter;
|
|
167
|
+
sort?: QuerySort;
|
|
168
|
+
limit?: number;
|
|
169
|
+
cursor?: string;
|
|
170
|
+
};
|
|
171
|
+
export type QueryResult = {
|
|
172
|
+
records: StackRecord[];
|
|
173
|
+
cursor: string | null;
|
|
174
|
+
total: number;
|
|
175
|
+
};
|
|
176
|
+
export type MigrationFn = (content: Record<string, unknown>) => Record<string, unknown>;
|
|
177
|
+
export type Migration = {
|
|
178
|
+
from: TypeId;
|
|
179
|
+
to: TypeId;
|
|
180
|
+
migrate: MigrationFn;
|
|
181
|
+
};
|
|
182
|
+
export type AdapterCapabilities = {
|
|
183
|
+
fullTextSearch: boolean;
|
|
184
|
+
contentFieldQuery: boolean;
|
|
185
|
+
sortableFields: Array<QuerySort['field']>;
|
|
186
|
+
};
|
|
187
|
+
/**
|
|
188
|
+
* Every storage backend implements this interface.
|
|
189
|
+
* The Stack class is a thin orchestration layer on top.
|
|
190
|
+
*/
|
|
191
|
+
export interface StackAdapter {
|
|
192
|
+
readonly capabilities: AdapterCapabilities;
|
|
193
|
+
getConfig(key: string): Promise<string | null>;
|
|
194
|
+
setConfig(key: string, value: string): Promise<void>;
|
|
195
|
+
createRecord(record: StackRecord): Promise<StackRecord>;
|
|
196
|
+
getRecord(id: RecordId): Promise<StackRecord | null>;
|
|
197
|
+
updateRecord(id: RecordId, changes: Partial<StackRecord>): Promise<StackRecord>;
|
|
198
|
+
deleteRecord(id: RecordId, opts?: {
|
|
199
|
+
hard?: boolean;
|
|
200
|
+
}): Promise<void>;
|
|
201
|
+
queryRecords(query: StackQuery): Promise<QueryResult>;
|
|
202
|
+
associate(id: RecordId, association: Association): Promise<void>;
|
|
203
|
+
dissociate(id: RecordId, association: Association): Promise<void>;
|
|
204
|
+
getVersions(id: RecordId): Promise<RecordVersion[]>;
|
|
205
|
+
getVersion(id: RecordId, version: number): Promise<RecordVersion | null>;
|
|
206
|
+
saveVersion(id: RecordId, version: RecordVersion): Promise<void>;
|
|
207
|
+
saveType(type: StackType): Promise<void>;
|
|
208
|
+
getType(id: TypeId): Promise<StackType | null>;
|
|
209
|
+
listTypes(): Promise<StackType[]>;
|
|
210
|
+
putAttachment(data: Uint8Array, mimeType: string): Promise<FileId>;
|
|
211
|
+
getAttachment(fileId: FileId): Promise<Uint8Array>;
|
|
212
|
+
deleteAttachment(fileId: FileId): Promise<void>;
|
|
213
|
+
flush?(): Promise<void>;
|
|
214
|
+
close?(): Promise<void>;
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stack — Core Type Definitions
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* This file is the source of truth for all types in the
|
|
5
|
+
* Stack library. No logic lives here — just shapes.
|
|
6
|
+
*/
|
|
7
|
+
/** Reserved system type IDs */
|
|
8
|
+
export const SYSTEM_TYPES = {
|
|
9
|
+
ENTITY: '_entity',
|
|
10
|
+
APP: '_app',
|
|
11
|
+
GROUP: '_group',
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAiLH,+BAA+B;AAC/B,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,MAAM,EAAE,SAAS;IACjB,GAAG,EAAE,MAAM;IACX,KAAK,EAAE,QAAQ;CACP,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stack — Content Validation
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* Validates a Record's content object against a TypeSchema.
|
|
5
|
+
* Returns a list of validation errors — empty means valid.
|
|
6
|
+
*
|
|
7
|
+
* Validates recursively for array and object field kinds.
|
|
8
|
+
* Coercion is never performed — types must match exactly.
|
|
9
|
+
*/
|
|
10
|
+
import type { TypeSchema } from './types.js';
|
|
11
|
+
export type ValidationError = {
|
|
12
|
+
path: string;
|
|
13
|
+
message: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Validate content against a schema, collecting all errors.
|
|
17
|
+
* @param content - The record's content object
|
|
18
|
+
* @param schema - The TypeSchema to validate against
|
|
19
|
+
* @param prefix - Internal: dot path prefix for nested validation
|
|
20
|
+
* @param errors - Internal: error accumulator for recursive calls
|
|
21
|
+
*/
|
|
22
|
+
export declare const validateContent: (content: Record<string, unknown>, schema: TypeSchema, prefix?: string, errors?: ValidationError[]) => ValidationError[];
|
|
23
|
+
/**
|
|
24
|
+
* Returns true if the content is valid against the schema.
|
|
25
|
+
* Use validateContent() directly to get error details.
|
|
26
|
+
*/
|
|
27
|
+
export declare const isValid: (content: Record<string, unknown>, schema: TypeSchema) => boolean;
|
|
28
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,UAAU,EAA6B,MAAM,YAAY,CAAC;AAMxE,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAqEF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAC1B,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,QAAQ,UAAU,EAClB,eAAW,EACX,SAAQ,eAAe,EAAO,KAC7B,eAAe,EAiBjB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,OAAO,GAAI,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,UAAU,KAAG,OAChC,CAAC"}
|
package/dist/validate.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stack — Content Validation
|
|
3
|
+
* -------------------------------------------------------
|
|
4
|
+
* Validates a Record's content object against a TypeSchema.
|
|
5
|
+
* Returns a list of validation errors — empty means valid.
|
|
6
|
+
*
|
|
7
|
+
* Validates recursively for array and object field kinds.
|
|
8
|
+
* Coercion is never performed — types must match exactly.
|
|
9
|
+
*/
|
|
10
|
+
// -------------------------------------------------------
|
|
11
|
+
// Internal helpers
|
|
12
|
+
// -------------------------------------------------------
|
|
13
|
+
const jsTypeForScalar = (kind) => {
|
|
14
|
+
switch (kind) {
|
|
15
|
+
case 'string':
|
|
16
|
+
case 'text':
|
|
17
|
+
case 'record-ref':
|
|
18
|
+
return 'string';
|
|
19
|
+
case 'number':
|
|
20
|
+
return 'number';
|
|
21
|
+
case 'boolean':
|
|
22
|
+
return 'boolean';
|
|
23
|
+
case 'date':
|
|
24
|
+
return 'string'; // Dates are transmitted as ISO 8601 strings
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const validateField = (value, def, path, errors) => {
|
|
28
|
+
if (def.kind === 'array') {
|
|
29
|
+
if (!Array.isArray(value)) {
|
|
30
|
+
errors.push({ path, message: `Expected array, got ${typeof value}` });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
value.forEach((item, i) => validateField(item, def.items, `${path}[${i}]`, errors));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (def.kind === 'object') {
|
|
37
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
38
|
+
errors.push({ path, message: `Expected object, got ${typeof value}` });
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
validateContent(value, def.properties, path, errors);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Scalar validation
|
|
45
|
+
if (def.kind === 'date') {
|
|
46
|
+
if (typeof value !== 'string' || isNaN(Date.parse(value))) {
|
|
47
|
+
errors.push({
|
|
48
|
+
path,
|
|
49
|
+
message: `Expected ISO 8601 date string, got ${typeof value}`,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const expected = jsTypeForScalar(def.kind);
|
|
55
|
+
if (typeof value !== expected) {
|
|
56
|
+
errors.push({
|
|
57
|
+
path,
|
|
58
|
+
message: `Expected ${expected}, got ${typeof value}`,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
// -------------------------------------------------------
|
|
63
|
+
// Public API
|
|
64
|
+
// -------------------------------------------------------
|
|
65
|
+
/**
|
|
66
|
+
* Validate content against a schema, collecting all errors.
|
|
67
|
+
* @param content - The record's content object
|
|
68
|
+
* @param schema - The TypeSchema to validate against
|
|
69
|
+
* @param prefix - Internal: dot path prefix for nested validation
|
|
70
|
+
* @param errors - Internal: error accumulator for recursive calls
|
|
71
|
+
*/
|
|
72
|
+
export const validateContent = (content, schema, prefix = '', errors = []) => {
|
|
73
|
+
// Check all schema fields
|
|
74
|
+
for (const [key, def] of Object.entries(schema)) {
|
|
75
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
76
|
+
const value = content[key];
|
|
77
|
+
if (value === undefined || value === null) {
|
|
78
|
+
if (def.required) {
|
|
79
|
+
errors.push({ path, message: 'Required field is missing' });
|
|
80
|
+
}
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
validateField(value, def, path, errors);
|
|
84
|
+
}
|
|
85
|
+
return errors;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Returns true if the content is valid against the schema.
|
|
89
|
+
* Use validateContent() directly to get error details.
|
|
90
|
+
*/
|
|
91
|
+
export const isValid = (content, schema) => validateContent(content, schema).length === 0;
|
|
92
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@haverstack/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Core library for Haverstack — portable personal data stack",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"import": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/haverstack/core",
|
|
20
|
+
"directory": "packages/core"
|
|
21
|
+
},
|
|
22
|
+
"license": "CC0-1.0",
|
|
23
|
+
"keywords": [
|
|
24
|
+
"haverstack",
|
|
25
|
+
"personal data",
|
|
26
|
+
"data stack",
|
|
27
|
+
"structured data",
|
|
28
|
+
"records",
|
|
29
|
+
"portable"
|
|
30
|
+
],
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^22.0.0",
|
|
33
|
+
"typescript": "^5.5.0",
|
|
34
|
+
"vitest": "^2.0.0"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsc -p tsconfig.build.json",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"lint": "eslint src tests"
|
|
41
|
+
}
|
|
42
|
+
}
|