├── cfds ├── richtext │ ├── rtl.ts │ ├── markdown.ts │ ├── utils.ts │ └── tree-keys.ts ├── base │ ├── defs.ts │ ├── types │ │ ├── string-type.ts │ │ ├── date-type.ts │ │ ├── primitive-type.ts │ │ └── utils.ts │ └── listenable.ts └── change │ ├── decode.ts │ └── index.ts ├── docs ├── static │ ├── .nojekyll │ └── img │ │ ├── favicon.ico │ │ ├── favicon.png │ │ ├── goatdb_logo_dark_500.png │ │ ├── goatdb_logo_light_500.png │ │ └── favicon.svg ├── docs │ ├── docs │ │ └── api │ │ │ └── index.mdx │ ├── api │ │ ├── interfaces │ │ │ ├── coreobject.mdx │ │ │ ├── readonlyjsonobject.mdx │ │ │ ├── logstream.mdx │ │ │ ├── jsonobject.mdx │ │ │ ├── equatable.mdx │ │ │ ├── comparable.mdx │ │ │ ├── clonable.mdx │ │ │ ├── encodable.mdx │ │ │ ├── encoder.mdx │ │ │ ├── coreoptions.mdx │ │ │ ├── readonlyitem.mdx │ │ │ ├── usequeryopts.mdx │ │ │ ├── buildinfo.mdx │ │ │ ├── corevaluecloneopts.mdx │ │ │ ├── serverservices.mdx │ │ │ └── dbinstanceconfig.mdx │ │ ├── types │ │ │ ├── corekey.mdx │ │ │ ├── schemanulltype.mdx │ │ │ ├── debugserveroptions.mdx │ │ │ ├── authop.mdx │ │ │ ├── schematypesession.mdx │ │ │ ├── schematypeuserstats.mdx │ │ │ ├── coreset.mdx │ │ │ ├── corearray.mdx │ │ │ ├── useitemopts.mdx │ │ │ ├── dbreadystate.mdx │ │ │ ├── domainconfig.mdx │ │ │ ├── readonlycoreobject.mdx │ │ │ ├── propswithpath.mdx │ │ │ ├── readonlycorearray.mdx │ │ │ ├── emailinfo.mdx │ │ │ ├── schemafieldsdef.mdx │ │ │ ├── coredictionary.mdx │ │ │ ├── executableoptions.mdx │ │ │ ├── iterablefilterfunc.mdx │ │ │ ├── schemaoptionalfields.mdx │ │ │ ├── compileoptions.mdx │ │ │ ├── cpuarch.mdx │ │ │ ├── authrule.mdx │ │ │ ├── objfieldsfilterfunc.mdx │ │ │ ├── livereloadoptions.mdx │ │ │ ├── targetos.mdx │ │ │ ├── schemafield.mdx │ │ │ ├── authconfig.mdx │ │ │ ├── authruleinfo.mdx │ │ │ ├── schemavaluewithoptional.mdx │ │ │ ├── appconfig.mdx │ │ │ ├── emailbuilder.mdx │ │ │ ├── fielddef.mdx │ │ │ ├── schemarequiredfields.mdx │ │ │ ├── corevalue.mdx │ │ │ ├── coreclassobject.mdx │ │ │ ├── schemadatatype.mdx │ │ │ ├── fieldvalue.mdx │ │ │ ├── schema.mdx │ │ │ ├── queryconfig.mdx │ │ │ └── concretecorevalue.mdx │ │ ├── classes │ │ │ ├── consolelogstream.mdx │ │ │ ├── emitter.mdx │ │ │ ├── jsonlogstream.mdx │ │ │ └── trustpool.mdx │ │ ├── functions-server.mdx │ │ └── functions-react.mdx │ └── installation.md ├── api │ ├── interfaces │ │ ├── coreobject.mdx │ │ ├── readonlyjsonobject.mdx │ │ ├── jsonobject.mdx │ │ ├── logstream.mdx │ │ ├── equatable.mdx │ │ ├── clonable.mdx │ │ ├── comparable.mdx │ │ ├── encodable.mdx │ │ ├── encoder.mdx │ │ ├── coreoptions.mdx │ │ ├── readonlyitem.mdx │ │ ├── usequeryopts.mdx │ │ ├── corevaluecloneopts.mdx │ │ ├── buildinfo.mdx │ │ └── dbinstanceconfig.mdx │ ├── types │ │ ├── corekey.mdx │ │ ├── schemanulltype.mdx │ │ ├── debugserveroptions.mdx │ │ ├── authop.mdx │ │ ├── schematypesession.mdx │ │ ├── coreset.mdx │ │ ├── schematypeuserstats.mdx │ │ ├── corearray.mdx │ │ ├── useitemopts.mdx │ │ ├── dbreadystate.mdx │ │ ├── readonlycoreobject.mdx │ │ ├── propswithpath.mdx │ │ ├── readonlycorearray.mdx │ │ ├── emailinfo.mdx │ │ ├── coredictionary.mdx │ │ ├── schemafieldsdef.mdx │ │ ├── executableoptions.mdx │ │ ├── iterablefilterfunc.mdx │ │ ├── schemaoptionalfields.mdx │ │ ├── compileoptions.mdx │ │ ├── cpuarch.mdx │ │ ├── authrule.mdx │ │ ├── objfieldsfilterfunc.mdx │ │ ├── livereloadoptions.mdx │ │ ├── targetos.mdx │ │ ├── authconfig.mdx │ │ ├── schemafield.mdx │ │ ├── authruleinfo.mdx │ │ ├── emailbuilder.mdx │ │ ├── schemavaluewithoptional.mdx │ │ ├── appconfig.mdx │ │ ├── fielddef.mdx │ │ ├── corevalue.mdx │ │ ├── schemarequiredfields.mdx │ │ ├── coreclassobject.mdx │ │ ├── schemadatatype.mdx │ │ ├── fieldvalue.mdx │ │ ├── schema.mdx │ │ ├── queryconfig.mdx │ │ └── concretecorevalue.mdx │ ├── classes │ │ ├── consolelogstream.mdx │ │ ├── emitter.mdx │ │ ├── jsonlogstream.mdx │ │ ├── trustpool.mdx │ │ └── dataregistry.mdx │ ├── functions-server.mdx │ └── functions-react.mdx ├── tsconfig.json ├── .gitignore ├── src │ ├── components │ │ ├── Diagram.tsx │ │ ├── Diagram.module.css │ │ ├── icons │ │ │ ├── EphemeralCRDTIcon.tsx │ │ │ ├── SelfHealingIcon.tsx │ │ │ ├── DeployIcon.tsx │ │ │ ├── InstantUpdateIcon.tsx │ │ │ ├── TypeScriptIcon.tsx │ │ │ ├── TimeTravelIcon.tsx │ │ │ ├── OfflineIcon.tsx │ │ │ ├── ThreeWayMergeIcon.tsx │ │ │ ├── ConflictResolutionIcon.tsx │ │ │ ├── OfflineFirstIcon.tsx │ │ │ ├── VersionHistoryIcon.tsx │ │ │ ├── SingleTenantIcon.tsx │ │ │ ├── MultiplayerIcon.tsx │ │ │ └── CollaborativeIcon.tsx │ │ ├── QuickStart │ │ │ └── styles.module.css │ │ └── diagrams │ │ │ └── base │ │ │ ├── Node.tsx │ │ │ ├── Box.tsx │ │ │ └── Arrow.tsx │ └── css │ │ └── benchmark-tables.css ├── deno.json ├── package.json ├── README.md └── docs-build.ts ├── db ├── persistance │ ├── query-opfs.ts │ └── query-file.ts └── settings │ └── settings.ts ├── examples ├── nodejs-basic │ ├── .npmrc │ ├── package.json │ └── README.md └── README.md ├── assets └── favicon.png ├── base ├── localization.ts ├── json-log │ ├── json-log-worker-entry.ts │ └── file-impl-deno.ts ├── types.ts ├── path.ts ├── sleep.ts ├── core-types │ ├── encoding │ │ ├── utils.ts │ │ ├── index.ts │ │ ├── hash.ts │ │ └── types.ts │ ├── index.ts │ └── utils.ts ├── debounce.ts ├── map.ts ├── json.ts ├── interfaces.ts ├── error.ts ├── browser.ts ├── bloom-test.ts ├── delayed-action.ts ├── rendezvous-hash.ts ├── tuple.ts ├── async.ts ├── coroutine-timer.ts ├── math.ts ├── collections │ ├── queue.ts │ └── lru-cache.ts ├── version-number.ts ├── process-manager.ts ├── cache.ts ├── parallel-executor.ts ├── lru-cache.ts ├── node-runner.ts └── os.ts ├── system-assets ├── bloom_filter.wasm ├── system-assets.ts └── build-sys-assets.ts ├── benchmarks ├── browser │ ├── assets │ │ ├── sqlite3.wasm │ │ └── sqlite3-worker1.js │ └── debug-server-entry.ts └── benchmarks-entry-server.ts ├── logging ├── server-events.ts ├── log.ts └── json-log-stream.ts ├── .gitignore ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── FUNDING.yml └── workflows │ └── test.yml ├── net └── server │ ├── utils.ts │ ├── service.ts │ ├── cors.ts │ ├── health.ts │ └── logs.ts ├── tsconfig.json ├── external └── MurmurHash3.h ├── server ├── mod.ts ├── config.ts └── app-config.ts ├── tests ├── orderstamp-expose.test.ts └── browser │ └── debug-server-entry.ts └── deno.json /cfds/richtext/rtl.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /db/persistance/query-opfs.ts: -------------------------------------------------------------------------------- 1 | // TODO 2 | -------------------------------------------------------------------------------- /examples/nodejs-basic/.npmrc: -------------------------------------------------------------------------------- 1 | @jsr:registry=https://npm.jsr.io 2 | -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatplatform/goatdb/HEAD/assets/favicon.png -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatplatform/goatdb/HEAD/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /docs/static/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatplatform/goatdb/HEAD/docs/static/img/favicon.png -------------------------------------------------------------------------------- /base/localization.ts: -------------------------------------------------------------------------------- 1 | export function localizedStr(value: string, desc: string): string { 2 | return value; 3 | } 4 | -------------------------------------------------------------------------------- /system-assets/bloom_filter.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatplatform/goatdb/HEAD/system-assets/bloom_filter.wasm -------------------------------------------------------------------------------- /base/json-log/json-log-worker-entry.ts: -------------------------------------------------------------------------------- 1 | import { jsonLogWorkerMain } from './json-log-worker.ts'; 2 | 3 | jsonLogWorkerMain(); 4 | -------------------------------------------------------------------------------- /benchmarks/browser/assets/sqlite3.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatplatform/goatdb/HEAD/benchmarks/browser/assets/sqlite3.wasm -------------------------------------------------------------------------------- /docs/static/img/goatdb_logo_dark_500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatplatform/goatdb/HEAD/docs/static/img/goatdb_logo_dark_500.png -------------------------------------------------------------------------------- /docs/static/img/goatdb_logo_light_500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goatplatform/goatdb/HEAD/docs/static/img/goatdb_logo_light_500.png -------------------------------------------------------------------------------- /base/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Make all properties in T writable. 3 | * @internal 4 | */ 5 | export type Readwrite = { 6 | -readonly [P in keyof T]: T[P]; 7 | }; 8 | -------------------------------------------------------------------------------- /docs/docs/docs/api/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | title: API Reference 4 | --- 5 | 6 | # API Reference 7 | 8 | Complete API documentation for GoatDB. 9 | 10 | -------------------------------------------------------------------------------- /base/path.ts: -------------------------------------------------------------------------------- 1 | import * as path from '@std/path'; 2 | 3 | export function toAbsolutePath(p: string): string { 4 | if (path.isAbsolute(p)) { 5 | return p; 6 | } 7 | return path.join(Deno.cwd(), p); 8 | } 9 | -------------------------------------------------------------------------------- /base/sleep.ts: -------------------------------------------------------------------------------- 1 | export function msleep(n: number): void { 2 | Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, n); 3 | } 4 | 5 | export function sleep(n: number): void { 6 | msleep(n * 1000); 7 | } 8 | -------------------------------------------------------------------------------- /docs/api/interfaces/coreobject.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CoreObject 3 | sidebar_label: CoreObject 4 | --- 5 | 6 | # CoreObject 7 | 8 | A mutable object with string keys and optional type parameter for values 9 | 10 | -------------------------------------------------------------------------------- /docs/docs/api/interfaces/coreobject.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CoreObject 3 | sidebar_label: CoreObject 4 | --- 5 | 6 | # CoreObject 7 | 8 | A mutable object with string keys and optional type parameter for values 9 | 10 | -------------------------------------------------------------------------------- /docs/api/interfaces/readonlyjsonobject.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: ReadonlyJSONObject 3 | sidebar_label: ReadonlyJSONObject 4 | --- 5 | 6 | # ReadonlyJSONObject 7 | 8 | **Extended by:** [JSONObject](/api/interfaces/jsonobject) 9 | 10 | -------------------------------------------------------------------------------- /docs/api/types/corekey.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CoreKey 3 | sidebar_label: CoreKey 4 | --- 5 | 6 | # CoreKey 7 | 8 | A string key type for CoreObjects 9 | 10 | ## Definition 11 | 12 | **Type:** CoreKey = any 13 | 14 | -------------------------------------------------------------------------------- /docs/api/types/schemanulltype.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: SchemaNullType 3 | sidebar_label: SchemaNullType 4 | --- 5 | 6 | # SchemaNullType 7 | 8 | ## Definition 9 | 10 | **Type:** SchemaNullType = typeof kNullSchema 11 | 12 | -------------------------------------------------------------------------------- /docs/docs/api/types/corekey.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CoreKey 3 | sidebar_label: CoreKey 4 | --- 5 | 6 | # CoreKey 7 | 8 | A string key type for CoreObjects 9 | 10 | ## Definition 11 | 12 | **Type:** CoreKey = any 13 | 14 | -------------------------------------------------------------------------------- /docs/api/types/debugserveroptions.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: DebugServerOptions 3 | sidebar_label: DebugServerOptions 4 | --- 5 | 6 | # DebugServerOptions 7 | 8 | ## Definition 9 | 10 | **Type:** DebugServerOptions = any 11 | 12 | -------------------------------------------------------------------------------- /docs/docs/api/interfaces/readonlyjsonobject.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: ReadonlyJSONObject 3 | sidebar_label: ReadonlyJSONObject 4 | --- 5 | 6 | # ReadonlyJSONObject 7 | 8 | **Extended by:** [JSONObject](/docs/api/interfaces/jsonobject) 9 | 10 | -------------------------------------------------------------------------------- /docs/docs/api/types/schemanulltype.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: SchemaNullType 3 | sidebar_label: SchemaNullType 4 | --- 5 | 6 | # SchemaNullType 7 | 8 | ## Definition 9 | 10 | **Type:** SchemaNullType = typeof kNullSchema 11 | 12 | -------------------------------------------------------------------------------- /docs/docs/api/types/debugserveroptions.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: DebugServerOptions 3 | sidebar_label: DebugServerOptions 4 | --- 5 | 6 | # DebugServerOptions 7 | 8 | ## Definition 9 | 10 | **Type:** DebugServerOptions = any 11 | 12 | -------------------------------------------------------------------------------- /docs/api/types/authop.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: AuthOp 3 | sidebar_label: AuthOp 4 | --- 5 | 6 | # AuthOp 7 | 8 | Denotes the type of the requested operation. 9 | 10 | ## Definition 11 | 12 | **Type:** AuthOp = 'read' | 'write' 13 | 14 | -------------------------------------------------------------------------------- /docs/api/types/schematypesession.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: SchemaTypeSession 3 | sidebar_label: SchemaTypeSession 4 | --- 5 | 6 | # SchemaTypeSession 7 | 8 | ## Definition 9 | 10 | **Type:** SchemaTypeSession = typeof kSchemaSession 11 | 12 | -------------------------------------------------------------------------------- /docs/docs/api/types/authop.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: AuthOp 3 | sidebar_label: AuthOp 4 | --- 5 | 6 | # AuthOp 7 | 8 | Denotes the type of the requested operation. 9 | 10 | ## Definition 11 | 12 | **Type:** AuthOp = 'read' | 'write' 13 | 14 | -------------------------------------------------------------------------------- /docs/docs/api/types/schematypesession.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: SchemaTypeSession 3 | sidebar_label: SchemaTypeSession 4 | --- 5 | 6 | # SchemaTypeSession 7 | 8 | ## Definition 9 | 10 | **Type:** SchemaTypeSession = typeof kSchemaSession 11 | 12 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@docusaurus/tsconfig", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | }, 7 | "exclude": [".docusaurus", "build"] 8 | } 9 | -------------------------------------------------------------------------------- /docs/api/types/coreset.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CoreSet 3 | sidebar_label: CoreSet 4 | --- 5 | 6 | # CoreSet 7 | 8 | A Set containing CoreValues 9 | 10 | ## Definition 11 | 12 | **Type:** CoreSet = Set<[CoreValue](/api/types/corevalue)> 13 | 14 | -------------------------------------------------------------------------------- /docs/api/types/schematypeuserstats.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: SchemaTypeUserStats 3 | sidebar_label: SchemaTypeUserStats 4 | --- 5 | 6 | # SchemaTypeUserStats 7 | 8 | ## Definition 9 | 10 | **Type:** SchemaTypeUserStats = typeof kSchemaUserStats 11 | 12 | -------------------------------------------------------------------------------- /docs/api/types/corearray.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CoreArray 3 | sidebar_label: CoreArray 4 | --- 5 | 6 | # CoreArray 7 | 8 | A mutable array of CoreValues 9 | 10 | ## Definition 11 | 12 | **Type:** CoreArray = [CoreValue](/api/types/corevalue)[] 13 | 14 | -------------------------------------------------------------------------------- /docs/docs/api/types/schematypeuserstats.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: SchemaTypeUserStats 3 | sidebar_label: SchemaTypeUserStats 4 | --- 5 | 6 | # SchemaTypeUserStats 7 | 8 | ## Definition 9 | 10 | **Type:** SchemaTypeUserStats = typeof kSchemaUserStats 11 | 12 | -------------------------------------------------------------------------------- /docs/docs/api/types/coreset.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CoreSet 3 | sidebar_label: CoreSet 4 | --- 5 | 6 | # CoreSet 7 | 8 | A Set containing CoreValues 9 | 10 | ## Definition 11 | 12 | **Type:** CoreSet = Set<[CoreValue](/docs/api/types/corevalue)> 13 | 14 | -------------------------------------------------------------------------------- /docs/api/interfaces/jsonobject.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: JSONObject 3 | sidebar_label: JSONObject 4 | --- 5 | 6 | # JSONObject 7 | 8 | **Extends:** [ReadonlyJSONObject](/api/interfaces/readonlyjsonobject) 9 | 10 | **Extended by:** [BuildInfo](/api/interfaces/buildinfo) 11 | 12 | -------------------------------------------------------------------------------- /docs/docs/api/types/corearray.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CoreArray 3 | sidebar_label: CoreArray 4 | --- 5 | 6 | # CoreArray 7 | 8 | A mutable array of CoreValues 9 | 10 | ## Definition 11 | 12 | **Type:** CoreArray = [CoreValue](/docs/api/types/corevalue)[] 13 | 14 | -------------------------------------------------------------------------------- /logging/server-events.ts: -------------------------------------------------------------------------------- 1 | import { BaseLogEntry } from './entry.ts'; 2 | 3 | export type ServerEvent = 'TempLoginTokenSent'; 4 | 5 | export interface ServerEventEntry extends BaseLogEntry { 6 | severity: 'EVENT'; 7 | event: ServerEvent; 8 | userId?: string; 9 | } 10 | -------------------------------------------------------------------------------- /docs/api/interfaces/logstream.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: LogStream 3 | sidebar_label: LogStream 4 | --- 5 | 6 | # LogStream 7 | 8 | ## Methods 9 | 10 | ### appendEntry() 11 | 12 | **appendEntry(e: NormalizedLogEntry<LogEntry>): void | Promise<void>** 13 | 14 | -------------------------------------------------------------------------------- /docs/api/types/useitemopts.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: UseItemOpts 3 | sidebar_label: UseItemOpts 4 | --- 5 | 6 | # UseItemOpts 7 | 8 | Options for the useItem hook. 9 | 10 | ## Definition 11 | 12 | **Type:** UseItemOpts = { keys?: string | string[] } 13 | 14 | -------------------------------------------------------------------------------- /docs/docs/api/interfaces/logstream.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: LogStream 3 | sidebar_label: LogStream 4 | --- 5 | 6 | # LogStream 7 | 8 | ## Methods 9 | 10 | ### appendEntry() 11 | 12 | **appendEntry(e: NormalizedLogEntry<LogEntry>): void | Promise<void>** 13 | 14 | -------------------------------------------------------------------------------- /docs/docs/api/types/useitemopts.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: UseItemOpts 3 | sidebar_label: UseItemOpts 4 | --- 5 | 6 | # UseItemOpts 7 | 8 | Options for the useItem hook. 9 | 10 | ## Definition 11 | 12 | **Type:** UseItemOpts = { keys?: string | string[] } 13 | 14 | -------------------------------------------------------------------------------- /docs/api/types/dbreadystate.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: DBReadyState 3 | sidebar_label: DBReadyState 4 | --- 5 | 6 | # DBReadyState 7 | 8 | Represents the state of the loading process. 9 | 10 | ## Definition 11 | 12 | **Type:** DBReadyState = 'loading' | 'ready' | 'error' 13 | 14 | -------------------------------------------------------------------------------- /docs/docs/api/interfaces/jsonobject.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: JSONObject 3 | sidebar_label: JSONObject 4 | --- 5 | 6 | # JSONObject 7 | 8 | **Extends:** [ReadonlyJSONObject](/docs/api/interfaces/readonlyjsonobject) 9 | 10 | **Extended by:** [BuildInfo](/docs/api/interfaces/buildinfo) 11 | 12 | -------------------------------------------------------------------------------- /docs/docs/api/types/dbreadystate.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: DBReadyState 3 | sidebar_label: DBReadyState 4 | --- 5 | 6 | # DBReadyState 7 | 8 | Represents the state of the loading process. 9 | 10 | ## Definition 11 | 12 | **Type:** DBReadyState = 'loading' | 'ready' | 'error' 13 | 14 | -------------------------------------------------------------------------------- /docs/api/types/readonlycoreobject.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: ReadonlyCoreObject 3 | sidebar_label: ReadonlyCoreObject 4 | --- 5 | 6 | # ReadonlyCoreObject 7 | 8 | A read-only object with string keys and CoreValue values 9 | 10 | ## Definition 11 | 12 | **Type:** ReadonlyCoreObject = object 13 | 14 | -------------------------------------------------------------------------------- /docs/docs/api/types/domainconfig.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: DomainConfig 3 | sidebar_label: DomainConfig 4 | --- 5 | 6 | # DomainConfig 7 | 8 | ## Definition 9 | 10 | **Type:** DomainConfig = { resolveDomain: (domain: string) => string; resolveOrg: (orgId: string) => string } 11 | 12 | -------------------------------------------------------------------------------- /docs/api/types/propswithpath.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: PropsWithPath 3 | sidebar_label: PropsWithPath 4 | --- 5 | 6 | # PropsWithPath 7 | 8 | Convenience props type for components that accept a DB path as input. 9 | 10 | ## Definition 11 | 12 | **Type:** PropsWithPath = { path: string } 13 | 14 | -------------------------------------------------------------------------------- /docs/api/types/readonlycorearray.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: ReadonlyCoreArray 3 | sidebar_label: ReadonlyCoreArray 4 | --- 5 | 6 | # ReadonlyCoreArray 7 | 8 | A read-only array of CoreValues 9 | 10 | ## Definition 11 | 12 | **Type:** ReadonlyCoreArray = readonly [CoreValue](/api/types/corevalue)[] 13 | 14 | -------------------------------------------------------------------------------- /docs/docs/api/types/readonlycoreobject.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: ReadonlyCoreObject 3 | sidebar_label: ReadonlyCoreObject 4 | --- 5 | 6 | # ReadonlyCoreObject 7 | 8 | A read-only object with string keys and CoreValue values 9 | 10 | ## Definition 11 | 12 | **Type:** ReadonlyCoreObject = object 13 | 14 | -------------------------------------------------------------------------------- /docs/docs/api/types/propswithpath.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: PropsWithPath 3 | sidebar_label: PropsWithPath 4 | --- 5 | 6 | # PropsWithPath 7 | 8 | Convenience props type for components that accept a DB path as input. 9 | 10 | ## Definition 11 | 12 | **Type:** PropsWithPath = { path: string } 13 | 14 | -------------------------------------------------------------------------------- /docs/api/types/emailinfo.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: EmailInfo 3 | sidebar_label: EmailInfo 4 | --- 5 | 6 | # EmailInfo 7 | 8 | Union type for all supported email types. 9 | Currently only supports login emails with magic links. 10 | 11 | ## Definition 12 | 13 | **Type:** EmailInfo = EmailLoginWithMagicLink 14 | 15 | -------------------------------------------------------------------------------- /docs/docs/api/types/readonlycorearray.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: ReadonlyCoreArray 3 | sidebar_label: ReadonlyCoreArray 4 | --- 5 | 6 | # ReadonlyCoreArray 7 | 8 | A read-only array of CoreValues 9 | 10 | ## Definition 11 | 12 | **Type:** ReadonlyCoreArray = readonly [CoreValue](/docs/api/types/corevalue)[] 13 | 14 | -------------------------------------------------------------------------------- /base/core-types/encoding/utils.ts: -------------------------------------------------------------------------------- 1 | import { ConstructorDecoderConfig } from './types.ts'; 2 | 3 | export function isDecoderConfig>( 4 | v: unknown, 5 | ): v is T { 6 | // deno-lint-ignore no-prototype-builtins 7 | return v !== undefined && v !== null && v.hasOwnProperty('decoder'); 8 | } 9 | -------------------------------------------------------------------------------- /docs/docs/api/types/emailinfo.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: EmailInfo 3 | sidebar_label: EmailInfo 4 | --- 5 | 6 | # EmailInfo 7 | 8 | Union type for all supported email types. 9 | Currently only supports login emails with magic links. 10 | 11 | ## Definition 12 | 13 | **Type:** EmailInfo = EmailLoginWithMagicLink 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | test_data 4 | build 5 | _site 6 | .jekyll-cache 7 | temp_bench_* 8 | # Code Index MCP cache directory 9 | .code_indexer/ 10 | .mem 11 | .zed 12 | # ChunkHound cache directory 13 | .chunkhound/ 14 | .chunkhound.json 15 | 16 | # API docs build artifacts 17 | docs/api.json 18 | 19 | .claude/agents 20 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /docs/api/types/coredictionary.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CoreDictionary 3 | sidebar_label: CoreDictionary 4 | --- 5 | 6 | # CoreDictionary 7 | 8 | A dictionary of CoreKeys to CoreValues 9 | 10 | ## Definition 11 | 12 | **Type:** CoreDictionary = Dictionary<[CoreKey](/api/types/corekey), [CoreValue](/api/types/corevalue)> 13 | 14 | -------------------------------------------------------------------------------- /docs/api/types/schemafieldsdef.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: SchemaFieldsDef 3 | sidebar_label: SchemaFieldsDef 4 | --- 5 | 6 | # SchemaFieldsDef 7 | 8 | Mapping between field name and its definition. 9 | 10 | ## Definition 11 | 12 | **Type:** SchemaFieldsDef = Record<string, [FieldDef](/api/types/fielddef)<ValueType>> 13 | 14 | -------------------------------------------------------------------------------- /base/core-types/encoding/index.ts: -------------------------------------------------------------------------------- 1 | export type { 2 | ReadonlyDecodedObject, 3 | DecodedValue, 4 | DecodableKey, 5 | ReadonlyDecodedArray, 6 | Decoder, 7 | Decodable, 8 | ConstructorDecoderConfig, 9 | } from './types.ts'; 10 | 11 | export { isDecoderConfig } from './utils.ts'; 12 | 13 | export { encodableValueHash } from './hash.ts'; 14 | -------------------------------------------------------------------------------- /docs/docs/api/types/schemafieldsdef.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: SchemaFieldsDef 3 | sidebar_label: SchemaFieldsDef 4 | --- 5 | 6 | # SchemaFieldsDef 7 | 8 | Mapping between field name and its definition. 9 | 10 | ## Definition 11 | 12 | **Type:** SchemaFieldsDef = Record<string, [FieldDef](/docs/api/types/fielddef)<ValueType>> 13 | 14 | -------------------------------------------------------------------------------- /docs/api/types/executableoptions.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: ExecutableOptions 3 | sidebar_label: ExecutableOptions 4 | --- 5 | 6 | # ExecutableOptions 7 | 8 | ## Definition 9 | 10 | **Type:** ExecutableOptions = { arch?: [CPUArch](/api/types/cpuarch); os?: [TargetOS](/api/types/targetos); outputName?: string; serverEntry: string } 11 | 12 | -------------------------------------------------------------------------------- /docs/docs/api/types/coredictionary.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CoreDictionary 3 | sidebar_label: CoreDictionary 4 | --- 5 | 6 | # CoreDictionary 7 | 8 | A dictionary of CoreKeys to CoreValues 9 | 10 | ## Definition 11 | 12 | **Type:** CoreDictionary = Dictionary<[CoreKey](/docs/api/types/corekey), [CoreValue](/docs/api/types/corevalue)> 13 | 14 | -------------------------------------------------------------------------------- /docs/api/interfaces/equatable.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Equatable 3 | sidebar_label: Equatable 4 | --- 5 | 6 | # Equatable 7 | 8 | Interface for objects that can determine equality with other objects 9 | Allows custom equality logic beyond reference equality 10 | 11 | ## Methods 12 | 13 | ### isEqual() 14 | 15 | **isEqual(other: T): boolean** 16 | 17 | -------------------------------------------------------------------------------- /docs/api/types/iterablefilterfunc.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: IterableFilterFunc 3 | sidebar_label: IterableFilterFunc 4 | --- 5 | 6 | # IterableFilterFunc 7 | 8 | Function type for filtering iterable values during operations 9 | 10 | ## Definition 11 | 12 | **Type:** IterableFilterFunc = (value: [CoreValue](/api/types/corevalue)) => boolean 13 | 14 | -------------------------------------------------------------------------------- /docs/docs/api/interfaces/equatable.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Equatable 3 | sidebar_label: Equatable 4 | --- 5 | 6 | # Equatable 7 | 8 | Interface for objects that can determine equality with other objects 9 | Allows custom equality logic beyond reference equality 10 | 11 | ## Methods 12 | 13 | ### isEqual() 14 | 15 | **isEqual(other: T): boolean** 16 | 17 | -------------------------------------------------------------------------------- /docs/docs/api/types/executableoptions.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: ExecutableOptions 3 | sidebar_label: ExecutableOptions 4 | --- 5 | 6 | # ExecutableOptions 7 | 8 | ## Definition 9 | 10 | **Type:** ExecutableOptions = { arch?: [CPUArch](/docs/api/types/cpuarch); os?: [TargetOS](/docs/api/types/targetos); outputName?: string; serverEntry: string } 11 | 12 | -------------------------------------------------------------------------------- /docs/docs/api/types/iterablefilterfunc.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: IterableFilterFunc 3 | sidebar_label: IterableFilterFunc 4 | --- 5 | 6 | # IterableFilterFunc 7 | 8 | Function type for filtering iterable values during operations 9 | 10 | ## Definition 11 | 12 | **Type:** IterableFilterFunc = (value: [CoreValue](/docs/api/types/corevalue)) => boolean 13 | 14 | -------------------------------------------------------------------------------- /base/debounce.ts: -------------------------------------------------------------------------------- 1 | export function debounce( 2 | fn: T, 3 | delay = 0 4 | ) { 5 | let timeoutId: any; 6 | return (args: K) => { 7 | if (timeoutId) { 8 | clearTimeout(timeoutId); 9 | } 10 | timeoutId = setTimeout(() => { 11 | fn(args); 12 | timeoutId = null; 13 | }, delay); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /docs/api/types/schemaoptionalfields.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: SchemaOptionalFields 3 | sidebar_label: SchemaOptionalFields 4 | --- 5 | 6 | # SchemaOptionalFields 7 | 8 | Given a schema, extracts the names of all optional fields. 9 | 10 | ## Definition 11 | 12 | **Type:** SchemaOptionalFields = T['fields'][K]['required'] extends false | undefined ? K : never 13 | 14 | -------------------------------------------------------------------------------- /docs/api/interfaces/clonable.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Clonable 3 | sidebar_label: Clonable 4 | --- 5 | 6 | # Clonable 7 | 8 | Interface for objects that can be cloned 9 | Essential for copy operations in diffing and patching workflows 10 | 11 | ## Methods 12 | 13 | ### clone() 14 | 15 | **clone(opts: [CoreValueCloneOpts](/api/interfaces/corevaluecloneopts)): T** 16 | 17 | -------------------------------------------------------------------------------- /docs/api/interfaces/comparable.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Comparable 3 | sidebar_label: Comparable 4 | --- 5 | 6 | # Comparable 7 | 8 | Interface for objects that can be compared for ordering 9 | Implementing this interface allows custom classes to define their own comparison logic 10 | 11 | ## Methods 12 | 13 | ### compare() 14 | 15 | **compare(other: T): number** 16 | 17 | -------------------------------------------------------------------------------- /docs/api/types/compileoptions.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CompileOptions 3 | sidebar_label: CompileOptions 4 | --- 5 | 6 | # CompileOptions 7 | 8 | Options for compiling a GoatDB application into a standalone executable. 9 | 10 | This combines executable build options with application configuration. 11 | 12 | ## Definition 13 | 14 | **Type:** CompileOptions = any 15 | 16 | -------------------------------------------------------------------------------- /docs/api/types/cpuarch.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CPUArch 3 | sidebar_label: CPUArch 4 | --- 5 | 6 | # CPUArch 7 | 8 | Represents the CPU architecture for compilation. 9 | 10 | - 'x64': 64-bit x86 architecture (Intel/AMD) 11 | - 'arm64': 64-bit ARM architecture (Apple Silicon, ARM-based servers) 12 | 13 | ## Definition 14 | 15 | **Type:** CPUArch = 'x64' | 'arm64' 16 | 17 | -------------------------------------------------------------------------------- /docs/docs/api/types/schemaoptionalfields.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: SchemaOptionalFields 3 | sidebar_label: SchemaOptionalFields 4 | --- 5 | 6 | # SchemaOptionalFields 7 | 8 | Given a schema, extracts the names of all optional fields. 9 | 10 | ## Definition 11 | 12 | **Type:** SchemaOptionalFields = T['fields'][K]['required'] extends false | undefined ? K : never 13 | 14 | -------------------------------------------------------------------------------- /docs/api/types/authrule.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: AuthRule 3 | sidebar_label: AuthRule 4 | --- 5 | 6 | # AuthRule 7 | 8 | A function that determines whether a specific operation is authorized. 9 | Returns true if the operation is allowed, false otherwise. 10 | 11 | ## Definition 12 | 13 | **Type:** AuthRule = (info: [AuthRuleInfo](/api/types/authruleinfo)) => boolean 14 | 15 | -------------------------------------------------------------------------------- /docs/docs/api/interfaces/comparable.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Comparable 3 | sidebar_label: Comparable 4 | --- 5 | 6 | # Comparable 7 | 8 | Interface for objects that can be compared for ordering 9 | Implementing this interface allows custom classes to define their own comparison logic 10 | 11 | ## Methods 12 | 13 | ### compare() 14 | 15 | **compare(other: T): number** 16 | 17 | -------------------------------------------------------------------------------- /docs/docs/api/types/compileoptions.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CompileOptions 3 | sidebar_label: CompileOptions 4 | --- 5 | 6 | # CompileOptions 7 | 8 | Options for compiling a GoatDB application into a standalone executable. 9 | 10 | This combines executable build options with application configuration. 11 | 12 | ## Definition 13 | 14 | **Type:** CompileOptions = any 15 | 16 | -------------------------------------------------------------------------------- /docs/docs/api/types/cpuarch.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CPUArch 3 | sidebar_label: CPUArch 4 | --- 5 | 6 | # CPUArch 7 | 8 | Represents the CPU architecture for compilation. 9 | 10 | - 'x64': 64-bit x86 architecture (Intel/AMD) 11 | - 'arm64': 64-bit ARM architecture (Apple Silicon, ARM-based servers) 12 | 13 | ## Definition 14 | 15 | **Type:** CPUArch = 'x64' | 'arm64' 16 | 17 | -------------------------------------------------------------------------------- /docs/api/types/objfieldsfilterfunc.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: ObjFieldsFilterFunc 3 | sidebar_label: ObjFieldsFilterFunc 4 | --- 5 | 6 | # ObjFieldsFilterFunc 7 | 8 | Function type for filtering object fields during operations 9 | 10 | ## Definition 11 | 12 | **Type:** ObjFieldsFilterFunc = (key: string, obj: [ReadonlyCoreObject](/api/types/readonlycoreobject)) => boolean 13 | 14 | -------------------------------------------------------------------------------- /docs/docs/api/interfaces/clonable.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Clonable 3 | sidebar_label: Clonable 4 | --- 5 | 6 | # Clonable 7 | 8 | Interface for objects that can be cloned 9 | Essential for copy operations in diffing and patching workflows 10 | 11 | ## Methods 12 | 13 | ### clone() 14 | 15 | **clone(opts: [CoreValueCloneOpts](/docs/api/interfaces/corevaluecloneopts)): T** 16 | 17 | -------------------------------------------------------------------------------- /db/settings/settings.ts: -------------------------------------------------------------------------------- 1 | import type { OwnedSession, Session } from '../session.ts'; 2 | 3 | export interface DBSettings { 4 | currentSession: OwnedSession; 5 | roots: Session[]; 6 | trustedSessions: Session[]; 7 | } 8 | 9 | export interface DBSettingsProvider { 10 | readonly settings: DBSettings; 11 | load(): Promise; 12 | update(settings: DBSettings): Promise; 13 | } 14 | -------------------------------------------------------------------------------- /docs/docs/api/types/authrule.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: AuthRule 3 | sidebar_label: AuthRule 4 | --- 5 | 6 | # AuthRule 7 | 8 | A function that determines whether a specific operation is authorized. 9 | Returns true if the operation is allowed, false otherwise. 10 | 11 | ## Definition 12 | 13 | **Type:** AuthRule = (info: [AuthRuleInfo](/docs/api/types/authruleinfo)) => boolean 14 | 15 | -------------------------------------------------------------------------------- /docs/api/types/livereloadoptions.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: LiveReloadOptions 3 | sidebar_label: LiveReloadOptions 4 | --- 5 | 6 | # LiveReloadOptions 7 | 8 | ## Definition 9 | 10 | **Type:** LiveReloadOptions = { afterBuild?: () => Promise<void>; beforeBuild?: () => Promise<void>; orgId?: string; watchDir?: string; watchFilter?: (path: string) => boolean } 11 | 12 | -------------------------------------------------------------------------------- /docs/api/types/targetos.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: TargetOS 3 | sidebar_label: TargetOS 4 | --- 5 | 6 | # TargetOS 7 | 8 | Represents the target operating system for compilation. 9 | 10 | - 'mac': macOS operating system 11 | - 'linux': Linux operating system 12 | - 'windows': Windows operating system 13 | 14 | ## Definition 15 | 16 | **Type:** TargetOS = 'mac' | 'linux' | 'windows' 17 | 18 | -------------------------------------------------------------------------------- /docs/docs/api/types/objfieldsfilterfunc.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: ObjFieldsFilterFunc 3 | sidebar_label: ObjFieldsFilterFunc 4 | --- 5 | 6 | # ObjFieldsFilterFunc 7 | 8 | Function type for filtering object fields during operations 9 | 10 | ## Definition 11 | 12 | **Type:** ObjFieldsFilterFunc = (key: string, obj: [ReadonlyCoreObject](/docs/api/types/readonlycoreobject)) => boolean 13 | 14 | -------------------------------------------------------------------------------- /docs/docs/api/types/livereloadoptions.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: LiveReloadOptions 3 | sidebar_label: LiveReloadOptions 4 | --- 5 | 6 | # LiveReloadOptions 7 | 8 | ## Definition 9 | 10 | **Type:** LiveReloadOptions = { afterBuild?: () => Promise<void>; beforeBuild?: () => Promise<void>; orgId?: string; watchDir?: string; watchFilter?: (path: string) => boolean } 11 | 12 | -------------------------------------------------------------------------------- /docs/docs/api/types/targetos.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: TargetOS 3 | sidebar_label: TargetOS 4 | --- 5 | 6 | # TargetOS 7 | 8 | Represents the target operating system for compilation. 9 | 10 | - 'mac': macOS operating system 11 | - 'linux': Linux operating system 12 | - 'windows': Windows operating system 13 | 14 | ## Definition 15 | 16 | **Type:** TargetOS = 'mac' | 'linux' | 'windows' 17 | 18 | -------------------------------------------------------------------------------- /docs/api/interfaces/encodable.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Encodable 3 | sidebar_label: Encodable 4 | --- 5 | 6 | # Encodable 7 | 8 | Interface for objects that can be encoded/serialized 9 | Allows custom classes to control their serialization behavior 10 | 11 | ## Methods 12 | 13 | ### serialize() 14 | 15 | **serialize(encoder: [Encoder](/api/interfaces/encoder)<K, V, T>, options: OT): void** 16 | 17 | -------------------------------------------------------------------------------- /docs/api/types/authconfig.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: AuthConfig 3 | sidebar_label: AuthConfig 4 | --- 5 | 6 | # AuthConfig 7 | 8 | An array of authentication rules for the full DB. The DB scans these rules 9 | and will use the first one that matches the repository's path. 10 | 11 | ## Definition 12 | 13 | **Type:** AuthConfig = { rule: [AuthRule](/api/types/authrule); rulePath: RegExp | string }[] 14 | 15 | -------------------------------------------------------------------------------- /docs/api/types/schemafield.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: SchemaField 3 | sidebar_label: SchemaField 4 | --- 5 | 6 | # SchemaField 7 | 8 | Represents a field in a schema, which can be either a custom field defined 9 | in the schema or one of the built-in fields that are automatically added to 10 | all items. 11 | 12 | ## Definition 13 | 14 | **Type:** SchemaField = keyof T['fields'] | keyof typeof kBuiltinFields 15 | 16 | -------------------------------------------------------------------------------- /docs/docs/api/interfaces/encodable.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Encodable 3 | sidebar_label: Encodable 4 | --- 5 | 6 | # Encodable 7 | 8 | Interface for objects that can be encoded/serialized 9 | Allows custom classes to control their serialization behavior 10 | 11 | ## Methods 12 | 13 | ### serialize() 14 | 15 | **serialize(encoder: [Encoder](/docs/api/interfaces/encoder)<K, V, T>, options: OT): void** 16 | 17 | -------------------------------------------------------------------------------- /docs/api/types/authruleinfo.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: AuthRuleInfo 3 | sidebar_label: AuthRuleInfo 4 | --- 5 | 6 | # AuthRuleInfo 7 | 8 | Information passed to authentication rules to determine if an operation 9 | is allowed. 10 | 11 | ## Definition 12 | 13 | **Type:** AuthRuleInfo = { db: [GoatDB](/api/classes/goatdb); itemKey: string; op: [AuthOp](/api/types/authop); repoPath: string; session: Session } 14 | 15 | -------------------------------------------------------------------------------- /docs/api/types/emailbuilder.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: EmailBuilder 3 | sidebar_label: EmailBuilder 4 | --- 5 | 6 | # EmailBuilder 7 | 8 | Function type for building email messages from email info. 9 | Takes email info and server config and returns a formatted email message. 10 | 11 | ## Definition 12 | 13 | **Type:** EmailBuilder = (info: [EmailInfo](/api/types/emailinfo), config: ServerOptions) => EmailMessage 14 | 15 | -------------------------------------------------------------------------------- /docs/docs/api/types/schemafield.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: SchemaField 3 | sidebar_label: SchemaField 4 | --- 5 | 6 | # SchemaField 7 | 8 | Represents a field in a schema, which can be either a custom field defined 9 | in the schema or one of the built-in fields that are automatically added to 10 | all items. 11 | 12 | ## Definition 13 | 14 | **Type:** SchemaField = keyof T['fields'] | keyof typeof kBuiltinFields 15 | 16 | -------------------------------------------------------------------------------- /docs/docs/api/types/authconfig.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: AuthConfig 3 | sidebar_label: AuthConfig 4 | --- 5 | 6 | # AuthConfig 7 | 8 | An array of authentication rules for the full DB. The DB scans these rules 9 | and will use the first one that matches the repository's path. 10 | 11 | ## Definition 12 | 13 | **Type:** AuthConfig = { rule: [AuthRule](/docs/api/types/authrule); rulePath: RegExp | string }[] 14 | 15 | -------------------------------------------------------------------------------- /base/map.ts: -------------------------------------------------------------------------------- 1 | export function toObject(map: Map): any { 2 | let obj: any = {}; 3 | 4 | map.forEach(function (value, key) { 5 | obj[key] = value; 6 | }); 7 | 8 | return obj; 9 | } 10 | 11 | export function shallowClone(map: Map): Map { 12 | const newMap = new Map(); 13 | 14 | map.forEach(function (value, key) { 15 | newMap.set(key, value); 16 | }); 17 | 18 | return newMap; 19 | } 20 | -------------------------------------------------------------------------------- /docs/api/types/schemavaluewithoptional.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: SchemaValueWithOptional 3 | sidebar_label: SchemaValueWithOptional 4 | --- 5 | 6 | # SchemaValueWithOptional 7 | 8 | Given a type (FieldValue) and a required + default function, this generates 9 | the correct type or union with undefined. 10 | 11 | ## Definition 12 | 13 | **Type:** SchemaValueWithOptional = R extends true ? T : D extends true ? T : undefined | T 14 | 15 | -------------------------------------------------------------------------------- /docs/docs/api/types/authruleinfo.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: AuthRuleInfo 3 | sidebar_label: AuthRuleInfo 4 | --- 5 | 6 | # AuthRuleInfo 7 | 8 | Information passed to authentication rules to determine if an operation 9 | is allowed. 10 | 11 | ## Definition 12 | 13 | **Type:** AuthRuleInfo = { db: [GoatDB](/docs/api/classes/goatdb); itemKey: string; op: [AuthOp](/docs/api/types/authop); repoPath: string; session: Session } 14 | 15 | -------------------------------------------------------------------------------- /docs/docs/api/types/schemavaluewithoptional.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: SchemaValueWithOptional 3 | sidebar_label: SchemaValueWithOptional 4 | --- 5 | 6 | # SchemaValueWithOptional 7 | 8 | Given a type (FieldValue) and a required + default function, this generates 9 | the correct type or union with undefined. 10 | 11 | ## Definition 12 | 13 | **Type:** SchemaValueWithOptional = R extends true ? T : D extends true ? T : undefined | T 14 | 15 | -------------------------------------------------------------------------------- /docs/api/types/appconfig.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: AppConfig 3 | sidebar_label: AppConfig 4 | --- 5 | 6 | # AppConfig 7 | 8 | A configuration of a Single Page Application built on top GoatDB. 9 | 10 | ## Definition 11 | 12 | **Type:** AppConfig = { appName?: string; assetsFilter?: (path: string) => boolean; assetsPath?: string; buildDir: string; cssPath?: string; denoJson?: string; htmlPath?: string; jsPath: string; minify?: boolean } 13 | 14 | -------------------------------------------------------------------------------- /docs/docs/api/types/appconfig.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: AppConfig 3 | sidebar_label: AppConfig 4 | --- 5 | 6 | # AppConfig 7 | 8 | A configuration of a Single Page Application built on top GoatDB. 9 | 10 | ## Definition 11 | 12 | **Type:** AppConfig = { appName?: string; assetsFilter?: (path: string) => boolean; assetsPath?: string; buildDir: string; cssPath?: string; denoJson?: string; htmlPath?: string; jsPath: string; minify?: boolean } 13 | 14 | -------------------------------------------------------------------------------- /docs/docs/api/types/emailbuilder.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: EmailBuilder 3 | sidebar_label: EmailBuilder 4 | --- 5 | 6 | # EmailBuilder 7 | 8 | Function type for building email messages from email info. 9 | Takes email info and server config and returns a formatted email message. 10 | 11 | ## Definition 12 | 13 | **Type:** EmailBuilder = (info: [EmailInfo](/docs/api/types/emailinfo), config: [ServerOptions](/docs/api/interfaces/serveroptions)) => EmailMessage 14 | 15 | -------------------------------------------------------------------------------- /docs/api/types/fielddef.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: FieldDef 3 | sidebar_label: FieldDef 4 | --- 5 | 6 | # FieldDef 7 | 8 | A definition of a single field in a schema. 9 | 10 | ## Definition 11 | 12 | **Type:** FieldDef = { default?: (data: [ReadonlyCoreObject](/api/types/readonlycoreobject)) => [FieldValue](/api/types/fieldvalue)<T>; required?: boolean; type: T; validate?: (data: [ReadonlyCoreObject](/api/types/readonlycoreobject)) => boolean } 13 | 14 | -------------------------------------------------------------------------------- /docs/docs/api/types/fielddef.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: FieldDef 3 | sidebar_label: FieldDef 4 | --- 5 | 6 | # FieldDef 7 | 8 | A definition of a single field in a schema. 9 | 10 | ## Definition 11 | 12 | **Type:** FieldDef = { default?: (data: [ReadonlyCoreObject](/docs/api/types/readonlycoreobject)) => [FieldValue](/docs/api/types/fieldvalue)<T>; required?: boolean; type: T; validate?: (data: [ReadonlyCoreObject](/docs/api/types/readonlycoreobject)) => boolean } 13 | 14 | -------------------------------------------------------------------------------- /docs/api/interfaces/encoder.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Encoder 3 | sidebar_label: Encoder 4 | --- 5 | 6 | # Encoder 7 | 8 | Interface for objects that can encode values 9 | Used in serialization processes 10 | 11 | ## Methods 12 | 13 | ### getOutput() 14 | 15 | **getOutput(): T** 16 | 17 | ### newEncoder() 18 | 19 | **newEncoder(): [Encoder](/api/interfaces/encoder)<K, V, T, OT>** 20 | 21 | ### set() 22 | 23 | **set(key: K, value: V, options: OT): void** 24 | 25 | -------------------------------------------------------------------------------- /docs/docs/api/interfaces/encoder.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Encoder 3 | sidebar_label: Encoder 4 | --- 5 | 6 | # Encoder 7 | 8 | Interface for objects that can encode values 9 | Used in serialization processes 10 | 11 | ## Methods 12 | 13 | ### getOutput() 14 | 15 | **getOutput(): T** 16 | 17 | ### newEncoder() 18 | 19 | **newEncoder(): [Encoder](/docs/api/interfaces/encoder)<K, V, T, OT>** 20 | 21 | ### set() 22 | 23 | **set(key: K, value: V, options: OT): void** 24 | 25 | -------------------------------------------------------------------------------- /docs/api/types/corevalue.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CoreValue 3 | sidebar_label: CoreValue 4 | --- 5 | 6 | # CoreValue 7 | 8 | The complete set of values supported by GoatDB, including undefined 9 | These types form the foundation for diffing and patching operations 10 | 11 | ## Definition 12 | 13 | **Type:** CoreValue = undefined | [ConcreteCoreValue](/api/types/concretecorevalue) | [CoreSet](/api/types/coreset) | [CoreDictionary](/api/types/coredictionary) | Generator<[CoreValue](/api/types/corevalue)> 14 | 15 | -------------------------------------------------------------------------------- /docs/api/types/schemarequiredfields.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: SchemaRequiredFields 3 | sidebar_label: SchemaRequiredFields 4 | --- 5 | 6 | # SchemaRequiredFields 7 | 8 | Given a schema, extracts the names of all required fields. 9 | Note: For practical purposes, fields with a default function are treated 10 | as required from the type system. 11 | 12 | ## Definition 13 | 14 | **Type:** SchemaRequiredFields = T['fields'][K]['required'] extends true ? T['fields'][K]['default'] extends Function ? never : K : never 15 | 16 | -------------------------------------------------------------------------------- /docs/docs/api/types/schemarequiredfields.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: SchemaRequiredFields 3 | sidebar_label: SchemaRequiredFields 4 | --- 5 | 6 | # SchemaRequiredFields 7 | 8 | Given a schema, extracts the names of all required fields. 9 | Note: For practical purposes, fields with a default function are treated 10 | as required from the type system. 11 | 12 | ## Definition 13 | 14 | **Type:** SchemaRequiredFields = T['fields'][K]['required'] extends true ? T['fields'][K]['default'] extends Function ? never : K : never 15 | 16 | -------------------------------------------------------------------------------- /docs/src/components/Diagram.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './Diagram.module.css'; 3 | 4 | type DiagramProps = { 5 | children: React.ReactNode; 6 | title?: string; 7 | }; 8 | 9 | export default function Diagram({ children, title }: DiagramProps) { 10 | return ( 11 |
12 | {title &&
{title}
} 13 |
14 | {children} 15 |
16 |
17 | ); 18 | } -------------------------------------------------------------------------------- /docs/api/types/coreclassobject.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CoreClassObject 3 | sidebar_label: CoreClassObject 4 | --- 5 | 6 | # CoreClassObject 7 | 8 | Union type of custom class objects that can be handled by GoatDB 9 | Support for custom classes allows for complex data structures with custom behaviors 10 | 11 | ## Definition 12 | 13 | **Type:** CoreClassObject = [Comparable](/api/interfaces/comparable) | [Clonable](/api/interfaces/clonable) | [Equatable](/api/interfaces/equatable) | [Encodable](/api/interfaces/encodable) 14 | 15 | -------------------------------------------------------------------------------- /base/core-types/encoding/hash.ts: -------------------------------------------------------------------------------- 1 | import { CoreOptions, CoreValue } from '../base.ts'; 2 | import { 3 | ChecksumEncoderOpts, 4 | Murmur3Checksum, 5 | Murmur3Opts, 6 | } from './checksum.ts'; 7 | 8 | const kMurmurEncoder = new Murmur3Checksum({ typeSafe: false }); 9 | 10 | export function coreValueHash( 11 | v: CoreValue | object, 12 | options?: CoreOptions | ChecksumEncoderOpts 13 | ): string { 14 | return kMurmurEncoder.checksumForValue(v, options as Murmur3Opts); 15 | } 16 | 17 | export const encodableValueHash = coreValueHash; 18 | -------------------------------------------------------------------------------- /cfds/base/defs.ts: -------------------------------------------------------------------------------- 1 | import { DiffMatchPatch } from '../../external/diff-match-patch.ts'; 2 | 3 | export const kDMP = new DiffMatchPatch(); 4 | kDMP.Diff_Timeout = 0.5; 5 | 6 | export const CFDS_VERSION = '4.0.0'; 7 | export const CFDS_COMP_MIN_VERSION = '4.0.0'; 8 | export const CFDS_COMP_MAX_VERSION = '4.0.0'; 9 | 10 | export enum ChangeType { 11 | Insert = 1, 12 | Other = 2, 13 | Equal = 0, 14 | Delete = -1, 15 | DeleteRange = -2, 16 | } 17 | 18 | export enum Dir { 19 | After = 1, 20 | Before = -1, 21 | Exact = 0, 22 | } 23 | -------------------------------------------------------------------------------- /docs/src/components/Diagram.module.css: -------------------------------------------------------------------------------- 1 | .diagramContainer { 2 | margin: 1rem 0; 3 | text-align: center; 4 | } 5 | 6 | .diagramTitle { 7 | font-size: 0.875rem; 8 | color: var(--ifm-color-emphasis-800); 9 | margin-bottom: 1rem; 10 | font-weight: 600; 11 | text-transform: uppercase; 12 | letter-spacing: 0.5px; 13 | } 14 | 15 | .diagramContent { 16 | display: inline-block; 17 | max-width: 100%; 18 | overflow-x: auto; 19 | } 20 | 21 | .diagramContent svg { 22 | max-width: 100%; 23 | height: auto; 24 | display: block; 25 | } -------------------------------------------------------------------------------- /docs/docs/api/types/corevalue.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CoreValue 3 | sidebar_label: CoreValue 4 | --- 5 | 6 | # CoreValue 7 | 8 | The complete set of values supported by GoatDB, including undefined 9 | These types form the foundation for diffing and patching operations 10 | 11 | ## Definition 12 | 13 | **Type:** CoreValue = undefined | [ConcreteCoreValue](/docs/api/types/concretecorevalue) | [CoreSet](/docs/api/types/coreset) | [CoreDictionary](/docs/api/types/coredictionary) | Generator<[CoreValue](/docs/api/types/corevalue)> 14 | 15 | -------------------------------------------------------------------------------- /docs/api/types/schemadatatype.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: SchemaDataType 3 | sidebar_label: SchemaDataType 4 | --- 5 | 6 | # SchemaDataType 7 | 8 | Given a schema, extracts the type of its data. 9 | 10 | ## Definition 11 | 12 | **Type:** SchemaDataType = { [k in K]: [SchemaValueWithOptional](/api/types/schemavaluewithoptional)<[FieldValue](/api/types/fieldvalue)<T['fields'][k]['type']>, T['fields'][k] extends { required: true } ? true : false, T['fields'][k] extends { default: Function } ? true : false> } 13 | 14 | -------------------------------------------------------------------------------- /docs/docs/api/types/coreclassobject.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CoreClassObject 3 | sidebar_label: CoreClassObject 4 | --- 5 | 6 | # CoreClassObject 7 | 8 | Union type of custom class objects that can be handled by GoatDB 9 | Support for custom classes allows for complex data structures with custom behaviors 10 | 11 | ## Definition 12 | 13 | **Type:** CoreClassObject = [Comparable](/docs/api/interfaces/comparable) | [Clonable](/docs/api/interfaces/clonable) | [Equatable](/docs/api/interfaces/equatable) | [Encodable](/docs/api/interfaces/encodable) 14 | 15 | -------------------------------------------------------------------------------- /examples/nodejs-basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "goatdb-nodejs-example", 3 | "version": "1.0.0", 4 | "description": "Basic Node.js example using GoatDB", 5 | "type": "module", 6 | "main": "index.js", 7 | "scripts": { 8 | "start": "node index.js", 9 | "dev": "node --watch index.js" 10 | }, 11 | "dependencies": { 12 | "@goatdb/goatdb": "npm:@jsr/goatdb__goatdb@^0.4.0" 13 | }, 14 | "devDependencies": { 15 | "@types/node": "^20.0.0", 16 | "typescript": "^5.0.0" 17 | }, 18 | "engines": { 19 | "node": ">=16.0.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/api/interfaces/coreoptions.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CoreOptions 3 | sidebar_label: CoreOptions 4 | --- 5 | 6 | # CoreOptions 7 | 8 | Options for controlling behavior of core operations 9 | 10 | **Extended by:** [CoreValueCloneOpts](/api/interfaces/corevaluecloneopts) 11 | 12 | ## Properties 13 | 14 | ### iterableFilter 15 | 16 | ```typescript 17 | iterableFilter: [IterableFilterFunc](/api/types/iterablefilterfunc) 18 | ``` 19 | 20 | ### objectFilterFields 21 | 22 | ```typescript 23 | objectFilterFields: [ObjFieldsFilterFunc](/api/types/objfieldsfilterfunc) 24 | ``` 25 | 26 | -------------------------------------------------------------------------------- /docs/docs/api/types/schemadatatype.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: SchemaDataType 3 | sidebar_label: SchemaDataType 4 | --- 5 | 6 | # SchemaDataType 7 | 8 | Given a schema, extracts the type of its data. 9 | 10 | ## Definition 11 | 12 | **Type:** SchemaDataType = { [k in K]: [SchemaValueWithOptional](/docs/api/types/schemavaluewithoptional)<[FieldValue](/docs/api/types/fieldvalue)<T['fields'][k]['type']>, T['fields'][k] extends { required: true } ? true : false, T['fields'][k] extends { default: Function } ? true : false> } 13 | 14 | -------------------------------------------------------------------------------- /docs/api/types/fieldvalue.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: FieldValue 3 | sidebar_label: FieldValue 4 | --- 5 | 6 | # FieldValue 7 | 8 | A mapping between a schema type and its native variable type. 9 | 10 | ## Definition 11 | 12 | **Type:** FieldValue = T extends 'string' ? string : T extends 'number' ? number : T extends 'boolean' ? boolean : T extends 'date' ? Date : T extends 'set' ? Set<[CoreValue](/api/types/corevalue)> : T extends 'map' ? Dictionary<string, [CoreValue](/api/types/corevalue)> : T extends 'richtext' ? RichText : [CoreValue](/api/types/corevalue) 13 | 14 | -------------------------------------------------------------------------------- /docs/docs/api/interfaces/coreoptions.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CoreOptions 3 | sidebar_label: CoreOptions 4 | --- 5 | 6 | # CoreOptions 7 | 8 | Options for controlling behavior of core operations 9 | 10 | **Extended by:** [CoreValueCloneOpts](/docs/api/interfaces/corevaluecloneopts) 11 | 12 | ## Properties 13 | 14 | ### iterableFilter 15 | 16 | ```typescript 17 | iterableFilter: [IterableFilterFunc](/docs/api/types/iterablefilterfunc) 18 | ``` 19 | 20 | ### objectFilterFields 21 | 22 | ```typescript 23 | objectFilterFields: [ObjFieldsFilterFunc](/docs/api/types/objfieldsfilterfunc) 24 | ``` 25 | 26 | -------------------------------------------------------------------------------- /docs/docs/api/types/fieldvalue.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: FieldValue 3 | sidebar_label: FieldValue 4 | --- 5 | 6 | # FieldValue 7 | 8 | A mapping between a schema type and its native variable type. 9 | 10 | ## Definition 11 | 12 | **Type:** FieldValue = T extends 'string' ? string : T extends 'number' ? number : T extends 'boolean' ? boolean : T extends 'date' ? Date : T extends 'set' ? Set<[CoreValue](/docs/api/types/corevalue)> : T extends 'map' ? Dictionary<string, [CoreValue](/docs/api/types/corevalue)> : T extends 'richtext' ? RichText : [CoreValue](/docs/api/types/corevalue) 13 | 14 | -------------------------------------------------------------------------------- /cfds/base/types/string-type.ts: -------------------------------------------------------------------------------- 1 | import { ValueType } from './index.ts'; 2 | import { CoreType } from '../../../base/core-types/index.ts'; 3 | import { PrimitiveTypeOperations } from './primitive-type.ts'; 4 | 5 | export class StringTypeOperations extends PrimitiveTypeOperations { 6 | // constructor(readonly isRef: boolean) { 7 | // super(CoreType.String, isRef ? ValueType.REF : ValueType.STRING); 8 | // } 9 | // fillRefs(refs: Set, value: string | undefined): void { 10 | // if (this.isRef && typeof value === 'string' && value.length > 0) { 11 | // refs.add(value); 12 | // } 13 | // } 14 | } 15 | -------------------------------------------------------------------------------- /docs/api/types/schema.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Schema 3 | sidebar_label: Schema 4 | --- 5 | 6 | # Schema 7 | 8 | A Schema defines the structure of a Document. Schemas are also versioned, 9 | allowing for live, gradual migrations of data for some users, while others 10 | continue to work with the old version in parallel. 11 | 12 | ## Definition 13 | 14 | **Type:** Schema = { fields: [SchemaFieldsDef](/api/types/schemafieldsdef); ns: null | string; upgrade?: (data: [ReadonlyCoreObject](/api/types/readonlycoreobject), schema: [Schema](/api/types/schema)) => [CoreObject](/api/interfaces/coreobject); version: number } 15 | 16 | -------------------------------------------------------------------------------- /docs/api/types/queryconfig.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: QueryConfig 3 | sidebar_label: QueryConfig 4 | --- 5 | 6 | # QueryConfig 7 | 8 | The configuration for a query, specifying the database, source, predicate, 9 | sort descriptor, schema, id, context, and limit. 10 | 11 | ## Definition 12 | 13 | **Type:** QueryConfig = { ctx?: CTX; db: [GoatDB](/api/classes/goatdb)<[Schema](/api/types/schema)>; id?: string; limit?: number; predicate?: Predicate<IS, CTX>; schema?: IS; sortBy?: SortDescriptor<OS, CTX> | keyof [SchemaDataType](/api/types/schemadatatype)<OS>; sortDescending?: boolean; source: QuerySource<IS, OS> } 14 | 15 | -------------------------------------------------------------------------------- /docs/docs/api/types/schema.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Schema 3 | sidebar_label: Schema 4 | --- 5 | 6 | # Schema 7 | 8 | A Schema defines the structure of a Document. Schemas are also versioned, 9 | allowing for live, gradual migrations of data for some users, while others 10 | continue to work with the old version in parallel. 11 | 12 | ## Definition 13 | 14 | **Type:** Schema = { fields: [SchemaFieldsDef](/docs/api/types/schemafieldsdef); ns: null | string; upgrade?: (data: [ReadonlyCoreObject](/docs/api/types/readonlycoreobject), schema: [Schema](/docs/api/types/schema)) => [CoreObject](/docs/api/interfaces/coreobject); version: number } 15 | 16 | -------------------------------------------------------------------------------- /docs/api/classes/consolelogstream.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: ConsoleLogStream 3 | sidebar_label: ConsoleLogStream 4 | --- 5 | 6 | # ConsoleLogStream 7 | 8 | A LogStream implementation that writes log entries to the console. 9 | Different severity levels are mapped to different console methods. 10 | 11 | **Implements:** [LogStream](/api/interfaces/logstream) 12 | 13 | ## Constructor 14 | 15 | **new ConsoleLogStream(severity: number | Severity)** 16 | 17 | Creates a new ConsoleLogStream 18 | 19 | ## Methods 20 | 21 | ### appendEntry() 22 | 23 | **appendEntry(e: NormalizedLogEntry<LogEntry>): void** 24 | 25 | Appends a log entry to the console 26 | 27 | -------------------------------------------------------------------------------- /docs/docs/api/types/queryconfig.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: QueryConfig 3 | sidebar_label: QueryConfig 4 | --- 5 | 6 | # QueryConfig 7 | 8 | The configuration for a query, specifying the database, source, predicate, 9 | sort descriptor, schema, id, context, and limit. 10 | 11 | ## Definition 12 | 13 | **Type:** QueryConfig = { ctx?: CTX; db: [GoatDB](/docs/api/classes/goatdb)<[Schema](/docs/api/types/schema)>; id?: string; limit?: number; predicate?: Predicate<IS, CTX>; schema?: IS; sortBy?: SortDescriptor<OS, CTX> | keyof [SchemaDataType](/docs/api/types/schemadatatype)<OS>; sortDescending?: boolean; source: QuerySource<IS, OS> } 14 | 15 | -------------------------------------------------------------------------------- /docs/docs/api/classes/consolelogstream.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: ConsoleLogStream 3 | sidebar_label: ConsoleLogStream 4 | --- 5 | 6 | # ConsoleLogStream 7 | 8 | A LogStream implementation that writes log entries to the console. 9 | Different severity levels are mapped to different console methods. 10 | 11 | **Implements:** [LogStream](/docs/api/interfaces/logstream) 12 | 13 | ## Constructor 14 | 15 | **new ConsoleLogStream(severity: number | Severity)** 16 | 17 | Creates a new ConsoleLogStream 18 | 19 | ## Methods 20 | 21 | ### appendEntry() 22 | 23 | **appendEntry(e: NormalizedLogEntry<LogEntry>): void** 24 | 25 | Appends a log entry to the console 26 | 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /docs/deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@goatdb/docs", 3 | "version": "0.1.0", 4 | "exports": { 5 | "./docs-api-build": "./docs-api-build.ts", 6 | "./docs-build": "./docs-build.ts", 7 | "./mdx-generator": "./mdx-generator.ts", 8 | "./type-formatter": "./type-formatter.ts" 9 | }, 10 | "compilerOptions": { 11 | "lib": ["dom", "dom.iterable", "dom.asynciterable", "deno.ns"] 12 | }, 13 | "imports": { 14 | "typedoc": "npm:typedoc@^0.28.8" 15 | }, 16 | "tasks": { 17 | "api": "deno run -A docs-api-build.ts", 18 | "build": "deno run -A docs-build.ts build", 19 | "serve": "deno run -A docs-build.ts serve" 20 | }, 21 | "exclude": ["node_modules/", ".docusaurus/", "build/", "docs/"] 22 | } -------------------------------------------------------------------------------- /base/core-types/index.ts: -------------------------------------------------------------------------------- 1 | export type { 2 | CoreOptions, 3 | Clonable, 4 | Comparable, 5 | CoreArray, 6 | CoreClassObject, 7 | CoreDictionary, 8 | CoreObject, 9 | CoreSet, 10 | CoreValue, 11 | Encodable, 12 | Encoder, 13 | Equatable, 14 | ReadonlyCoreArray, 15 | ReadonlyCoreObject, 16 | CoreValueCloneOpts, 17 | ConcreteCoreValue, 18 | CoreKey, 19 | } from './base.ts'; 20 | 21 | export { CoreType } from './base.ts'; 22 | 23 | export { 24 | getCoreType, 25 | isReadonlyCoreObject, 26 | getCoreTypeOrUndef, 27 | } from './utils.ts'; 28 | 29 | export { coreValueClone } from './clone.ts'; 30 | 31 | export { coreValueCompare } from './comparable.ts'; 32 | 33 | export { coreValueEquals } from './equals.ts'; 34 | -------------------------------------------------------------------------------- /base/json.ts: -------------------------------------------------------------------------------- 1 | import { JSONValue, ReadonlyJSONObject } from './interfaces.ts'; 2 | 3 | function formatPair(k: string, v: JSONValue): string { 4 | return `${JSON.stringify(k)}:${ 5 | typeof v === 'object' 6 | ? stableStringify(v as ReadonlyJSONObject) 7 | : JSON.stringify(v) 8 | }`; 9 | } 10 | 11 | export function stableStringify(obj: ReadonlyJSONObject): string { 12 | let res = '{'; 13 | const keys = Object.keys(obj).sort(); 14 | const len = keys.length; 15 | for (let i = 0; i < len - 1; ++i) { 16 | const k = keys[i]; 17 | res += formatPair(k, obj[k]) + ','; 18 | } 19 | if (len > 0) { 20 | const k = keys[len - 1]; 21 | res += formatPair(k, obj[k]); 22 | } 23 | res += '}'; 24 | return res; 25 | } 26 | -------------------------------------------------------------------------------- /base/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface ReadonlyJSONObject { 2 | readonly [key: string]: JSONValue; 3 | } 4 | 5 | export interface JSONObject extends ReadonlyJSONObject { 6 | [key: string]: JSONValue; 7 | } 8 | 9 | export type ReadonlyJSONArray = readonly ReadonlyJSONValue[]; 10 | 11 | export type JSONArray = JSONValue[]; 12 | 13 | export type ReadonlyJSONValue = 14 | | string 15 | | number 16 | | boolean 17 | | null 18 | | ReadonlyJSONArray 19 | | ReadonlyJSONObject 20 | | undefined; // For optional fields in interfaces 21 | 22 | export type JSONValue = 23 | | string 24 | | number 25 | | boolean 26 | | null 27 | | JSONArray 28 | | JSONObject 29 | | ReadonlyJSONValue 30 | | undefined; // For optional fields in interfaces 31 | -------------------------------------------------------------------------------- /cfds/change/decode.ts: -------------------------------------------------------------------------------- 1 | import { Change, ChangeType, EncodedChange } from './index.ts'; 2 | import { Decoder } from '../../base/core-types/encoding/index.ts'; 3 | import { FieldChange } from './field-change.ts'; 4 | import { RichTextChange } from './richtext-change.ts'; 5 | import { notReached } from '../../base/error.ts'; 6 | 7 | export function decodeChange( 8 | decoder: Decoder 9 | ): Change { 10 | const type = decoder.get('changeType') as ChangeType; 11 | switch (type) { 12 | case 'fd': 13 | return new FieldChange({ decoder }); 14 | case 'rt': 15 | return new RichTextChange({ decoder }); 16 | default: 17 | notReached('Unsupported format: ' + type); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /net/server/utils.ts: -------------------------------------------------------------------------------- 1 | import type { ServerServices } from './server.ts'; 2 | import type { Schema } from '../../cfds/base/schema.ts'; 3 | import { GoatRequest } from './http-compat.ts'; 4 | 5 | export function getRequestPath(req: GoatRequest): T { 6 | return new URL(req.url).pathname.toLowerCase() as T; 7 | } 8 | 9 | /** 10 | * Returns the base URL for the current application. 11 | * 12 | * @param services The services of the current server instance. 13 | * @returns A fully qualified base URL. 14 | */ 15 | export function getBaseURL( 16 | services: ServerServices, 17 | ): string { 18 | if (services.buildInfo.debugBuild) { 19 | return 'http://localhost:8080'; 20 | } 21 | return services.domain.resolveOrg(services.db.orgId); 22 | } 23 | -------------------------------------------------------------------------------- /docs/api/interfaces/readonlyitem.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: ReadonlyItem 3 | sidebar_label: ReadonlyItem 4 | --- 5 | 6 | # ReadonlyItem 7 | 8 | ## Properties 9 | 10 | ### checksum 11 | 12 | ```typescript 13 | checksum: string 14 | ``` 15 | 16 | ### isNull 17 | 18 | ```typescript 19 | isNull: boolean 20 | ``` 21 | 22 | ### isValid 23 | 24 | ```typescript 25 | isValid: boolean 26 | ``` 27 | 28 | ### schema 29 | 30 | ```typescript 31 | schema: S 32 | ``` 33 | 34 | ## Methods 35 | 36 | ### cloneData() 37 | 38 | **cloneData(): [SchemaDataType](/api/types/schemadatatype)<S>** 39 | 40 | ### get() 41 | 42 | **get(key: K): [SchemaDataType](/api/types/schemadatatype)<S>[K]** 43 | 44 | ### has() 45 | 46 | **has(key: keyof S['fields']): boolean** 47 | 48 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ESNext", 5 | "moduleResolution": "bundler", 6 | "allowImportingTsExtensions": true, 7 | "noEmit": true, 8 | "strict": false, 9 | "skipLibCheck": true, 10 | "downlevelIteration": true, 11 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 12 | "resolveJsonModule": true, 13 | "allowSyntheticDefaultImports": true, 14 | "esModuleInterop": true, 15 | "allowJs": true, 16 | "noImplicitAny": false 17 | }, 18 | "include": [ 19 | "mod.ts", 20 | "server/mod.ts", 21 | "react/hooks.ts" 22 | ], 23 | "exclude": [ 24 | "node_modules", 25 | "build", 26 | "dist", 27 | "docs", 28 | "examples", 29 | "tests", 30 | "benchmarks", 31 | "cpp" 32 | ] 33 | } -------------------------------------------------------------------------------- /docs/docs/api/interfaces/readonlyitem.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: ReadonlyItem 3 | sidebar_label: ReadonlyItem 4 | --- 5 | 6 | # ReadonlyItem 7 | 8 | ## Properties 9 | 10 | ### checksum 11 | 12 | ```typescript 13 | checksum: string 14 | ``` 15 | 16 | ### isNull 17 | 18 | ```typescript 19 | isNull: boolean 20 | ``` 21 | 22 | ### isValid 23 | 24 | ```typescript 25 | isValid: boolean 26 | ``` 27 | 28 | ### schema 29 | 30 | ```typescript 31 | schema: S 32 | ``` 33 | 34 | ## Methods 35 | 36 | ### cloneData() 37 | 38 | **cloneData(): [SchemaDataType](/docs/api/types/schemadatatype)<S>** 39 | 40 | ### get() 41 | 42 | **get(key: K): [SchemaDataType](/docs/api/types/schemadatatype)<S>[K]** 43 | 44 | ### has() 45 | 46 | **has(key: keyof S['fields']): boolean** 47 | 48 | -------------------------------------------------------------------------------- /docs/src/components/icons/EphemeralCRDTIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function EphemeralCRDTIcon({ className }: { className?: string }) { 4 | return ( 5 | 11 | 19 | 28 | 29 | ); 30 | } -------------------------------------------------------------------------------- /docs/src/components/icons/SelfHealingIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function SelfHealingIcon({ className }: { className?: string }) { 4 | return ( 5 | 11 | 19 | 28 | 29 | ); 30 | } -------------------------------------------------------------------------------- /docs/api/interfaces/usequeryopts.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: UseQueryOpts 3 | sidebar_label: UseQueryOpts 4 | --- 5 | 6 | # UseQueryOpts 7 | 8 | Options for the useQuery hook. 9 | 10 | **Extends:** [QueryConfig](/api/types/queryconfig) (excluding 'db') 11 | 12 | *Type composition: `Omit<[QueryConfig](/api/types/queryconfig), 'db'>`* 13 | 14 | ## Properties 15 | 16 | ### showIntermittentResults 17 | 18 | ```typescript 19 | showIntermittentResults: boolean 20 | ``` 21 | 22 | If `true`, updates UI during the initial scan. If `false`, waits until 23 | scanning is complete. 24 | 25 | ## Inherited Members 26 | 27 | ### From [QueryConfig](/api/types/queryconfig) 28 | 29 | *All properties and methods from QueryConfig except: `db`* 30 | 31 | *See [QueryConfig](/api/types/queryconfig) for detailed documentation* 32 | 33 | -------------------------------------------------------------------------------- /docs/docs/api/interfaces/usequeryopts.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: UseQueryOpts 3 | sidebar_label: UseQueryOpts 4 | --- 5 | 6 | # UseQueryOpts 7 | 8 | Options for the useQuery hook. 9 | 10 | **Extends:** [QueryConfig](/docs/api/types/queryconfig) (excluding 'db') 11 | 12 | *Type composition: `Omit<[QueryConfig](/docs/api/types/queryconfig), 'db'>`* 13 | 14 | ## Properties 15 | 16 | ### showIntermittentResults 17 | 18 | ```typescript 19 | showIntermittentResults: boolean 20 | ``` 21 | 22 | If `true`, updates UI during the initial scan. If `false`, waits until 23 | scanning is complete. 24 | 25 | ## Inherited Members 26 | 27 | ### From [QueryConfig](/docs/api/types/queryconfig) 28 | 29 | *All properties and methods from QueryConfig except: `db`* 30 | 31 | *See [QueryConfig](/docs/api/types/queryconfig) for detailed documentation* 32 | 33 | -------------------------------------------------------------------------------- /docs/src/components/QuickStart/styles.module.css: -------------------------------------------------------------------------------- 1 | .quickstart { 2 | padding: 3rem 0; 3 | background-color: var(--ifm-color-emphasis-100); 4 | } 5 | 6 | .quickstartHeader { 7 | text-align: center; 8 | margin-bottom: 2rem; 9 | } 10 | 11 | .quickstartHeader h2 { 12 | font-size: 2rem; 13 | margin-bottom: 0.5rem; 14 | } 15 | 16 | .quickstartHeader p { 17 | font-size: 1.1rem; 18 | color: var(--ifm-color-emphasis-700); 19 | margin: 0; 20 | } 21 | 22 | .codeTabs { 23 | margin-top: 2rem; 24 | } 25 | 26 | /* Make code blocks more prominent */ 27 | .quickstart pre { 28 | font-size: 0.875rem; 29 | border-radius: var(--ifm-code-border-radius); 30 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 31 | margin: 0; 32 | } 33 | 34 | /* Dark mode adjustments */ 35 | [data-theme='dark'] .quickstart pre { 36 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); 37 | } -------------------------------------------------------------------------------- /docs/api/types/concretecorevalue.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: ConcreteCoreValue 3 | sidebar_label: ConcreteCoreValue 4 | --- 5 | 6 | # ConcreteCoreValue 7 | 8 | Concrete (non-undefined) values supported by GoatDB 9 | Includes standard JSON types plus runtime JS types like Date, Set, 10 | Dictionary, and custom classes 11 | 12 | ## Definition 13 | 14 | **Type:** ConcreteCoreValue = string | number | boolean | null | Date | [CoreArray](/api/types/corearray) | [CoreObject](/api/interfaces/coreobject) | Set<[ConcreteCoreValue](/api/types/concretecorevalue)> | Dictionary<[CoreKey](/api/types/corekey), [ConcreteCoreValue](/api/types/concretecorevalue)> | Generator<[ConcreteCoreValue](/api/types/concretecorevalue)> | [CoreClassObject](/api/types/coreclassobject) | [ReadonlyCoreArray](/api/types/readonlycorearray) | [ReadonlyCoreObject](/api/types/readonlycoreobject) 15 | 16 | -------------------------------------------------------------------------------- /cfds/richtext/markdown.ts: -------------------------------------------------------------------------------- 1 | import { dfs, ElementNode, isTextNode } from './tree.ts'; 2 | 3 | export function treeToMarkdown(root: ElementNode | undefined): string { 4 | if (!root) { 5 | return ''; 6 | } 7 | let result = ''; 8 | let prevDepth = 1; 9 | for (const [node, depth] of dfs(root)) { 10 | if (depth !== prevDepth) { 11 | result += '\n'; 12 | prevDepth = depth; 13 | } 14 | switch (node.tagName) { 15 | case 'h1': 16 | result += '# '; 17 | break; 18 | 19 | case 'h2': 20 | result += '## '; 21 | break; 22 | 23 | case 'li': 24 | result += '* '; 25 | break; 26 | 27 | case 'ref': 28 | result += '- '; 29 | break; 30 | 31 | default: 32 | break; 33 | } 34 | if (isTextNode(node)) { 35 | result += node.text; 36 | } 37 | } 38 | return result; 39 | } 40 | -------------------------------------------------------------------------------- /net/server/service.ts: -------------------------------------------------------------------------------- 1 | import { assert } from '../../base/error.ts'; 2 | 3 | /** 4 | * Services are published by the server to enable all components to access the 5 | * relevant bits of data. For example database access, caches, etc can all be 6 | * exposed as server services. 7 | */ 8 | export class BaseService { 9 | private _services?: T; 10 | private _active = false; 11 | 12 | setup(services: T): Promise { 13 | this._services = services; 14 | return Promise.resolve(); 15 | } 16 | 17 | get services(): T { 18 | assert( 19 | this._services !== undefined, 20 | 'Did you forget to setup this service?' 21 | ); 22 | return this._services; 23 | } 24 | 25 | get active(): boolean { 26 | return this._active; 27 | } 28 | 29 | start(): void { 30 | this._active = true; 31 | } 32 | 33 | stop(): void { 34 | this._active = false; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /base/error.ts: -------------------------------------------------------------------------------- 1 | import { log } from '../logging/log.ts'; 2 | 3 | export function notImplemented(): never { 4 | const error = new Error('Not Implemented'); 5 | log({ severity: 'ERROR', error: 'NotImplemented', trace: error.stack }); 6 | throw error; 7 | } 8 | 9 | export function notReached(msg?: string): never { 10 | const error = new Error(msg); 11 | log({ 12 | severity: 'ERROR', 13 | error: 'NotReached', 14 | message: msg, 15 | trace: error.stack, 16 | }); 17 | throw error; 18 | } 19 | 20 | export function assert(condition: boolean, msg?: string): asserts condition { 21 | if (!condition) { 22 | msg = msg ? `Failed Assertion: "${msg}"` : 'Failed Assertion'; 23 | const error = new Error(msg); 24 | log({ 25 | severity: 'ERROR', 26 | error: 'FailedAssertion', 27 | message: msg, 28 | trace: error.stack, 29 | }); 30 | throw error; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs/docs/api/types/concretecorevalue.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: ConcreteCoreValue 3 | sidebar_label: ConcreteCoreValue 4 | --- 5 | 6 | # ConcreteCoreValue 7 | 8 | Concrete (non-undefined) values supported by GoatDB 9 | Includes standard JSON types plus runtime JS types like Date, Set, 10 | Dictionary, and custom classes 11 | 12 | ## Definition 13 | 14 | **Type:** ConcreteCoreValue = string | number | boolean | null | Date | [CoreArray](/docs/api/types/corearray) | [CoreObject](/docs/api/interfaces/coreobject) | Set<[ConcreteCoreValue](/docs/api/types/concretecorevalue)> | Dictionary<[CoreKey](/docs/api/types/corekey), [ConcreteCoreValue](/docs/api/types/concretecorevalue)> | Generator<[ConcreteCoreValue](/docs/api/types/concretecorevalue)> | [CoreClassObject](/docs/api/types/coreclassobject) | [ReadonlyCoreArray](/docs/api/types/readonlycorearray) | [ReadonlyCoreObject](/docs/api/types/readonlycoreobject) 15 | 16 | -------------------------------------------------------------------------------- /docs/src/components/icons/DeployIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function DeployIcon({ className }: { className?: string }) { 4 | return ( 5 | 11 | 21 | 27 | 35 | 36 | ); 37 | } -------------------------------------------------------------------------------- /docs/src/components/diagrams/base/Node.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from '../diagrams.module.css'; 3 | 4 | interface NodeProps { 5 | type: 'circle' | 'square' | 'diamond'; 6 | label: string; 7 | status?: 'active' | 'inactive' | 'processing' | 'error'; 8 | size?: 'small' | 'medium' | 'large'; 9 | className?: string; 10 | onClick?: () => void; 11 | } 12 | 13 | export default function Node({ 14 | type, 15 | label, 16 | status = 'active', 17 | size = 'medium', 18 | className = '', 19 | onClick 20 | }: NodeProps) { 21 | return ( 22 |
29 | {label} 30 |
31 | ); 32 | } -------------------------------------------------------------------------------- /base/browser.ts: -------------------------------------------------------------------------------- 1 | import { prettyJSON } from './common.ts'; 2 | import { ReadonlyJSONObject } from './interfaces.ts'; 3 | 4 | export function downloadJSON(fileName: string, json: ReadonlyJSONObject): void { 5 | if (typeof Deno !== 'undefined') { 6 | if (Deno.build.os === 'darwin') { 7 | const homeDir = Deno.env.get('HOME'); 8 | const outputPath = `${homeDir}/Downloads/${fileName}`; 9 | Deno.writeFileSync( 10 | outputPath, 11 | new TextEncoder().encode(JSON.stringify(json)) 12 | ); 13 | } 14 | return; 15 | } 16 | const jsonString = prettyJSON(json); 17 | const url = window.URL.createObjectURL( 18 | new Blob([jsonString], { type: 'text/json' }) 19 | ); 20 | const a = document.createElement('a'); 21 | a.style.display = 'none'; 22 | a.href = url; 23 | // the filename you want 24 | a.download = fileName; 25 | document.body.appendChild(a); 26 | a.click(); 27 | window.URL.revokeObjectURL(url); 28 | a.remove(); 29 | } 30 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: goatplatform # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /docs/src/components/diagrams/base/Box.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from '../diagrams.module.css'; 3 | 4 | interface BoxProps { 5 | label: string; 6 | color?: 'primary' | 'secondary' | 'success' | 'danger' | 'info'; 7 | size?: 'small' | 'medium' | 'large'; 8 | children?: React.ReactNode; 9 | className?: string; 10 | onClick?: () => void; 11 | } 12 | 13 | export default function Box({ 14 | label, 15 | color = 'primary', 16 | size = 'medium', 17 | children, 18 | className = '', 19 | onClick 20 | }: BoxProps) { 21 | return ( 22 |
29 |
{label}
30 | {children &&
{children}
} 31 |
32 | ); 33 | } -------------------------------------------------------------------------------- /base/bloom-test.ts: -------------------------------------------------------------------------------- 1 | import { uniqueId } from './common.ts'; 2 | import { BloomFilter } from './bloom.ts'; 3 | import { randomInt } from './math.ts'; 4 | 5 | function test(testSize: number): void { 6 | const values: string[] = []; 7 | for (let i = 0; i < testSize; ++i) { 8 | values.push(uniqueId()); 9 | } 10 | const checkCount = 2 * Math.ceil(Math.log2(testSize) / Math.log2(4)); 11 | let hitCount = 0; 12 | const testValue = uniqueId(); 13 | for (let i = 0; i < checkCount; ++i) { 14 | const filter = new BloomFilter({ size: testSize * 1.1, fpr: 0.25 }); 15 | filter.add(values); 16 | const salt: string[] = []; 17 | for (let j = 0; j < testSize * 0.1; ++j) { 18 | const v = uniqueId(); 19 | salt.push(v); 20 | filter.add(v); 21 | } 22 | if (filter.has(values[randomInt(0, salt.length)])) { 23 | // if (filter.has(testValue)) { 24 | ++hitCount; 25 | } 26 | } 27 | console.log(`Hit rate: ${hitCount}/${checkCount}`); 28 | } 29 | 30 | test(1000); 31 | -------------------------------------------------------------------------------- /cfds/richtext/utils.ts: -------------------------------------------------------------------------------- 1 | import { JSONObject, ReadonlyJSONObject } from '../../base/interfaces.ts'; 2 | import { 3 | JSONCyclicalDecoder, 4 | JSONCyclicalEncoder, 5 | } from '../../base/core-types/encoding/json.ts'; 6 | import { dfs, isTextNode, RichText, TextNode } from './tree.ts'; 7 | 8 | export function findFirstTextNode(rt: RichText): TextNode | undefined { 9 | for (const [node] of dfs(rt.root)) { 10 | if (isTextNode(node)) { 11 | return node; 12 | } 13 | } 14 | } 15 | 16 | export function richTextToJS(rt: RichText): JSONObject { 17 | const jsEncoder = new JSONCyclicalEncoder(); 18 | jsEncoder.set('rt', rt); 19 | 20 | const out = jsEncoder.getOutput() as ReadonlyJSONObject; 21 | const rtJS = out['rt'] as JSONObject; 22 | return rtJS; 23 | } 24 | 25 | export function jsToRichtext(rtJS: JSONObject): RichText { 26 | const jsDecoder = JSONCyclicalDecoder.get({ rt: rtJS }); 27 | const out = jsDecoder.get('rt') as RichText; 28 | jsDecoder.finalize(); 29 | return out; 30 | } 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /cfds/base/types/date-type.ts: -------------------------------------------------------------------------------- 1 | import { deserializeDate, serializeDate } from '../../../base/date.ts'; 2 | import { ValueTypeOptions } from './index.ts'; 3 | import { CoreType } from '../../../base/core-types/index.ts'; 4 | import { DecodedValue } from '../../../base/core-types/encoding/index.ts'; 5 | import { PrimitiveTypeOperations } from './primitive-type.ts'; 6 | import { Encoder } from '../../../base/core-types/base.ts'; 7 | 8 | export class DateTypeOperations extends PrimitiveTypeOperations { 9 | constructor() { 10 | super(CoreType.Date, 'date'); 11 | } 12 | 13 | deserialize(value: DecodedValue, options?: ValueTypeOptions) { 14 | const date = super.deserialize(value, options); 15 | if (typeof date === 'number') { 16 | return deserializeDate(date); 17 | } 18 | return date; 19 | } 20 | 21 | serialize( 22 | key: string, 23 | value: Date, 24 | encoder: Encoder, 25 | options?: ValueTypeOptions, 26 | ): void { 27 | encoder.set(key, serializeDate(value)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /docs/src/components/icons/InstantUpdateIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function InstantUpdateIcon({ className }: { className?: string }) { 4 | return ( 5 | 11 | 19 | 25 | 31 | 37 | 38 | ); 39 | } -------------------------------------------------------------------------------- /docs/src/components/icons/TypeScriptIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function TypeScriptIcon({ className }: { className?: string }) { 4 | return ( 5 | 11 | 21 | 28 | 35 | 36 | ); 37 | } -------------------------------------------------------------------------------- /docs/api/functions-server.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Server Functions 3 | --- 4 | 5 | # Server Functions 6 | 7 | ## compile 8 | 9 | **compile(options: [CompileOptions](/api/types/compileoptions)): Promise<void>** 10 | 11 | Compiles a GoatDB application into a standalone executable. 12 | 13 | This function performs the following steps: 14 | 1. Bundles client-side code (JS/TS/TSX) into a single JavaScript file 15 | 2. Processes static assets (HTML, CSS, images, etc.) 16 | 3. Generates build information 17 | 4. Compiles the server entry point into a native executable for the target 18 | platform 19 | 20 | ## startDebugServer 21 | 22 | **startDebugServer(options: [DebugServerOptions](/api/types/debugserveroptions)<US>): Promise<never>** 23 | 24 | Starts a local debug server. The debug server implements a live reload that 25 | automatically transpiles TypeScript and JSX using ESBuild. 26 | 27 | ## staticAssetsFromJS 28 | 29 | **staticAssetsFromJS(encodedAssets: [ReadonlyJSONObject](/api/interfaces/readonlyjsonobject)): StaticAssets** 30 | 31 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | push: 4 | branches: [ main ] 5 | pull_request: 6 | branches: [ main ] 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | 14 | - name: Setup Deno 15 | uses: denoland/setup-deno@v2 16 | with: 17 | deno-version: v2.x 18 | 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: '24.x' 23 | 24 | - name: Cache Playwright browsers 25 | uses: actions/cache@v4 26 | with: 27 | path: ~/.cache/ms-playwright 28 | key: ${{ runner.os }}-playwright-${{ hashFiles('deno.lock') }} 29 | restore-keys: | 30 | ${{ runner.os }}-playwright- 31 | 32 | - name: Install Playwright browsers and system dependencies 33 | run: deno run -A npm:playwright@^1.48.0 install --with-deps chromium 34 | 35 | - name: Run Tests 36 | run: deno task test 37 | env: 38 | CI: true -------------------------------------------------------------------------------- /docs/api/classes/emitter.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Emitter 3 | sidebar_label: Emitter 4 | --- 5 | 6 | # Emitter 7 | 8 | **Subclasses:** [GoatDB](/api/classes/goatdb), [ManagedItem](/api/classes/manageditem), [Query](/api/classes/query), [Repository](/api/classes/repository) 9 | 10 | ## Constructor 11 | 12 | **new Emitter(delayedEmissionTimerConstructor: (callback: TimerCallback) => Timer, alwaysActive: boolean)** 13 | 14 | ## Methods 15 | 16 | ### attach() 17 | 18 | **attach(e: E, c: C): () => void** 19 | 20 | ### detach() 21 | 22 | **detach(e: E, c: C): void** 23 | 24 | ### detachAll() 25 | 26 | **detachAll(e: E): void** 27 | 28 | ### emit() 29 | 30 | **emit(e: E, args: unknown[]): void** 31 | 32 | ### mute() 33 | 34 | **mute(): void** 35 | 36 | ### once() 37 | 38 | **once(e: E, c: C): () => void** 39 | 40 | ### resume() 41 | 42 | **resume(): void** 43 | 44 | ### suspend() 45 | 46 | **suspend(): void** 47 | 48 | ### unmute() 49 | 50 | **unmute(): void** 51 | 52 | -------------------------------------------------------------------------------- /docs/api/interfaces/corevaluecloneopts.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CoreValueCloneOpts 3 | sidebar_label: CoreValueCloneOpts 4 | --- 5 | 6 | # CoreValueCloneOpts 7 | 8 | Options for customizing cloning behavior 9 | 10 | **Extends:** [CoreOptions](/api/interfaces/coreoptions) 11 | 12 | ## Properties 13 | 14 | ### fieldCloneOverride 15 | 16 | ```typescript 17 | fieldCloneOverride: (obj: [ReadonlyCoreObject](/api/types/readonlycoreobject) | [CoreDictionary](/api/types/coredictionary), key: string, opts: [CoreValueCloneOpts](/api/interfaces/corevaluecloneopts)) => [CoreValue](/api/types/corevalue) 18 | ``` 19 | 20 | ### notClonableExt 21 | 22 | ```typescript 23 | notClonableExt: (obj: T) => T 24 | ``` 25 | 26 | ### objectOverride 27 | 28 | ```typescript 29 | objectOverride: (obj: [CoreValue](/api/types/corevalue)) => [boolean, [CoreValue](/api/types/corevalue)] 30 | ``` 31 | 32 | ## Inherited Members 33 | 34 | ### From [CoreOptions](/api/interfaces/coreoptions) 35 | 36 | **Properties:** `iterableFilter`, `objectFilterFields` 37 | 38 | *See [CoreOptions](/api/interfaces/coreoptions) for detailed documentation* 39 | 40 | -------------------------------------------------------------------------------- /docs/docs/api/functions-server.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Server Functions 3 | --- 4 | 5 | # Server Functions 6 | 7 | ## compile 8 | 9 | **compile(options: [CompileOptions](/docs/api/types/compileoptions)): Promise<void>** 10 | 11 | Compiles a GoatDB application into a standalone executable. 12 | 13 | This function performs the following steps: 14 | 1. Bundles client-side code (JS/TS/TSX) into a single JavaScript file 15 | 2. Processes static assets (HTML, CSS, images, etc.) 16 | 3. Generates build information 17 | 4. Compiles the server entry point into a native executable for the target 18 | platform 19 | 20 | ## startDebugServer 21 | 22 | **startDebugServer(options: [DebugServerOptions](/docs/api/types/debugserveroptions)<US>): Promise<never>** 23 | 24 | Starts a local debug server. The debug server implements a live reload that 25 | automatically transpiles TypeScript and JSX using ESBuild. 26 | 27 | ## staticAssetsFromJS 28 | 29 | **staticAssetsFromJS(encodedAssets: [ReadonlyJSONObject](/docs/api/interfaces/readonlyjsonobject)): StaticAssets** 30 | 31 | -------------------------------------------------------------------------------- /docs/src/components/icons/TimeTravelIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function TimeTravelIcon({ className }: { className?: string }) { 4 | return ( 5 | 11 | 19 | 26 | {/* */} 34 | {/* */} 40 | 41 | ); 42 | } -------------------------------------------------------------------------------- /docs/src/components/icons/OfflineIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function OfflineIcon({ className }: { className?: string }) { 4 | return ( 5 | 11 | 19 | 25 | 32 | 39 | 40 | ); 41 | } -------------------------------------------------------------------------------- /docs/api/interfaces/buildinfo.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: BuildInfo 3 | sidebar_label: BuildInfo 4 | --- 5 | 6 | # BuildInfo 7 | 8 | This interface encodes details about the build process used when compiling 9 | the app bundle. Used for internal configuration and later inspection. 10 | 11 | **Extends:** [JSONObject](/api/interfaces/jsonobject) 12 | 13 | ## Properties 14 | 15 | ### appVersion 16 | 17 | ```typescript 18 | appVersion: string 19 | ``` 20 | 21 | Application version, if available. Taken from the "version" field of the 22 | project's deno.json. 23 | 24 | ### builder 25 | 26 | ```typescript 27 | builder: BuilderInfo 28 | ``` 29 | 30 | Info about the builder runtime. 31 | 32 | ### createdBy 33 | 34 | ```typescript 35 | createdBy: string 36 | ``` 37 | 38 | The username that created the binary. 39 | 40 | ### creationDate 41 | 42 | ```typescript 43 | creationDate: string 44 | ``` 45 | 46 | When was the binary created. 47 | 48 | ### debugBuild 49 | 50 | ```typescript 51 | debugBuild: boolean 52 | ``` 53 | 54 | If true, indicates this is a debug build which turns off optimizations and 55 | turns on a debugging aids. 56 | 57 | -------------------------------------------------------------------------------- /docs/docs/api/classes/emitter.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Emitter 3 | sidebar_label: Emitter 4 | --- 5 | 6 | # Emitter 7 | 8 | **Subclasses:** [GoatDB](/docs/api/classes/goatdb), [ManagedItem](/docs/api/classes/manageditem), [Query](/docs/api/classes/query), [Repository](/docs/api/classes/repository) 9 | 10 | ## Constructor 11 | 12 | **new Emitter(delayedEmissionTimerConstructor: (callback: TimerCallback) => Timer, alwaysActive: boolean)** 13 | 14 | ## Methods 15 | 16 | ### attach() 17 | 18 | **attach(e: E, c: C): () => void** 19 | 20 | ### detach() 21 | 22 | **detach(e: E, c: C): void** 23 | 24 | ### detachAll() 25 | 26 | **detachAll(e: E): void** 27 | 28 | ### emit() 29 | 30 | **emit(e: E, args: unknown[]): void** 31 | 32 | ### mute() 33 | 34 | **mute(): void** 35 | 36 | ### once() 37 | 38 | **once(e: E, c: C): () => void** 39 | 40 | ### resume() 41 | 42 | **resume(): void** 43 | 44 | ### suspend() 45 | 46 | **suspend(): void** 47 | 48 | ### unmute() 49 | 50 | **unmute(): void** 51 | 52 | -------------------------------------------------------------------------------- /docs/src/css/benchmark-tables.css: -------------------------------------------------------------------------------- 1 | /* Benchmark table styling for performance comparisons */ 2 | 3 | /* Winner/Best performance highlighting */ 4 | .benchmark-table .winner { 5 | background-color: #e8f5e8; 6 | color: #2e7d2e; 7 | font-weight: 600; 8 | border-radius: 4px; 9 | padding: 3px 6px; 10 | border: 1px solid #4caf50; 11 | } 12 | 13 | /* Dark theme adjustments */ 14 | [data-theme='dark'] .benchmark-table .winner { 15 | background-color: rgba(76, 175, 80, 0.15); 16 | color: #81c784; 17 | border: 1px solid #4caf50; 18 | } 19 | 20 | /* Table styling improvements */ 21 | .benchmark-table { 22 | font-size: 0.9em; 23 | } 24 | 25 | .benchmark-table th { 26 | background-color: var(--ifm-color-emphasis-100); 27 | font-weight: 600; 28 | } 29 | 30 | .benchmark-table td { 31 | font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Consolas', monospace; 32 | font-size: 0.85em; 33 | white-space: nowrap; 34 | } 35 | 36 | /* Operation column stays readable font */ 37 | .benchmark-table td:first-child { 38 | font-family: var(--ifm-font-family-base); 39 | font-size: 0.9em; 40 | white-space: normal; 41 | } -------------------------------------------------------------------------------- /docs/docs/api/interfaces/buildinfo.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: BuildInfo 3 | sidebar_label: BuildInfo 4 | --- 5 | 6 | # BuildInfo 7 | 8 | This interface encodes details about the build process used when compiling 9 | the app bundle. Used for internal configuration and later inspection. 10 | 11 | **Extends:** [JSONObject](/docs/api/interfaces/jsonobject) 12 | 13 | ## Properties 14 | 15 | ### appVersion 16 | 17 | ```typescript 18 | appVersion: string 19 | ``` 20 | 21 | Application version, if available. Taken from the "version" field of the 22 | project's deno.json. 23 | 24 | ### builder 25 | 26 | ```typescript 27 | builder: BuilderInfo 28 | ``` 29 | 30 | Info about the builder runtime. 31 | 32 | ### createdBy 33 | 34 | ```typescript 35 | createdBy: string 36 | ``` 37 | 38 | The username that created the binary. 39 | 40 | ### creationDate 41 | 42 | ```typescript 43 | creationDate: string 44 | ``` 45 | 46 | When was the binary created. 47 | 48 | ### debugBuild 49 | 50 | ```typescript 51 | debugBuild: boolean 52 | ``` 53 | 54 | If true, indicates this is a debug build which turns off optimizations and 55 | turns on a debugging aids. 56 | 57 | -------------------------------------------------------------------------------- /docs/src/components/icons/ThreeWayMergeIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function ThreeWayMergeIcon({ className }: { className?: string }) { 4 | return ( 5 | 11 | 12 | 13 | 14 | 20 | 26 | 33 | 34 | ); 35 | } -------------------------------------------------------------------------------- /db/persistance/query-file.ts: -------------------------------------------------------------------------------- 1 | import * as path from '@std/path'; 2 | import { 3 | EncodedRepoCache, 4 | QueryPersistenceStorage, 5 | } from '../../repo/query-persistance.ts'; 6 | import { readTextFile, writeTextFile } from '../../base/json-log/json-log.ts'; 7 | 8 | export class QueryPersistenceFile implements QueryPersistenceStorage { 9 | constructor(readonly dir: string) {} 10 | 11 | async load(repoId: string): Promise { 12 | try { 13 | const text = await readTextFile( 14 | path.join(this.dir, repoId + '.cache.json'), 15 | ); 16 | return text && JSON.parse(text); 17 | } catch (_: unknown) { 18 | return undefined; 19 | } 20 | } 21 | 22 | async store(repoId: string, value: EncodedRepoCache): Promise { 23 | // try { 24 | const dst = path.join(this.dir, repoId + '.cache.json'); 25 | // await Deno.mkdir(path.dirname(dst), { recursive: true }); 26 | return await writeTextFile(dst, JSON.stringify(value)); 27 | // return true; 28 | // } catch (_: unknown) { 29 | // debugger; 30 | // return false; 31 | // } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /docs/docs/api/interfaces/corevaluecloneopts.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: CoreValueCloneOpts 3 | sidebar_label: CoreValueCloneOpts 4 | --- 5 | 6 | # CoreValueCloneOpts 7 | 8 | Options for customizing cloning behavior 9 | 10 | **Extends:** [CoreOptions](/docs/api/interfaces/coreoptions) 11 | 12 | ## Properties 13 | 14 | ### fieldCloneOverride 15 | 16 | ```typescript 17 | fieldCloneOverride: (obj: [ReadonlyCoreObject](/docs/api/types/readonlycoreobject) | [CoreDictionary](/docs/api/types/coredictionary), key: string, opts: [CoreValueCloneOpts](/docs/api/interfaces/corevaluecloneopts)) => [CoreValue](/docs/api/types/corevalue) 18 | ``` 19 | 20 | ### notClonableExt 21 | 22 | ```typescript 23 | notClonableExt: (obj: T) => T 24 | ``` 25 | 26 | ### objectOverride 27 | 28 | ```typescript 29 | objectOverride: (obj: [CoreValue](/docs/api/types/corevalue)) => [boolean, [CoreValue](/docs/api/types/corevalue)] 30 | ``` 31 | 32 | ## Inherited Members 33 | 34 | ### From [CoreOptions](/docs/api/interfaces/coreoptions) 35 | 36 | **Properties:** `iterableFilter`, `objectFilterFields` 37 | 38 | *See [CoreOptions](/docs/api/interfaces/coreoptions) for detailed documentation* 39 | 40 | -------------------------------------------------------------------------------- /docs/src/components/icons/ConflictResolutionIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function ConflictResolutionIcon({ className }: { className?: string }) { 4 | return ( 5 | 11 | 17 | 23 | 29 | 35 | 41 | 42 | ); 43 | } -------------------------------------------------------------------------------- /base/delayed-action.ts: -------------------------------------------------------------------------------- 1 | import { SimpleTimer, Timer } from './timer.ts'; 2 | 3 | export class DelayedAction { 4 | private readonly _timer: Timer; 5 | private _promise: Promise | undefined; 6 | private _resolve: ((v: T) => void) | undefined; 7 | private _reject: ((err: unknown) => void) | undefined; 8 | 9 | constructor( 10 | readonly delayMs: number, 11 | readonly callback: () => T | Promise, 12 | ) { 13 | this._timer = new SimpleTimer(delayMs, false, () => this._execute()); 14 | } 15 | 16 | schedule(): Promise { 17 | if (this._promise === undefined) { 18 | this._promise = new Promise((res, rej) => { 19 | this._resolve = res; 20 | this._reject = rej; 21 | }); 22 | this._timer.schedule(); 23 | } 24 | return this._promise; 25 | } 26 | 27 | private async _execute(): Promise { 28 | const res = this._resolve!; 29 | const rej = this._reject!; 30 | this._promise = undefined; 31 | this._resolve = undefined; 32 | this._reject = undefined; 33 | try { 34 | res(await this.callback()); 35 | } catch (err: unknown) { 36 | rej(err); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /docs/static/img/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /base/rendezvous-hash.ts: -------------------------------------------------------------------------------- 1 | import { murmur3 } from './hash.ts'; 2 | 3 | const K_MURMUR_SEED = 7366173373169631; 4 | 5 | export class RendezvousHash { 6 | private readonly _peers: Set; 7 | 8 | constructor(peers?: Iterable) { 9 | this._peers = new Set(peers); 10 | } 11 | 12 | addPeer(p: T): void { 13 | this._peers.add(p); 14 | } 15 | 16 | removePeer(p: T): void { 17 | this._peers.delete(p); 18 | } 19 | 20 | peerForKey(key: string | null): T | undefined { 21 | const peers = this._peers; 22 | if (!peers.size) { 23 | return undefined; 24 | } 25 | const entries: RendezvousEntry[] = []; 26 | const normalizedKey = key === null ? 'null' : `"${key}"`; 27 | for (const p of peers) { 28 | entries.push([p, murmur3(`${p}/${normalizedKey}`, K_MURMUR_SEED)]); 29 | } 30 | entries.sort(compareRendezvousEntries); 31 | return entries[0][0]; 32 | } 33 | } 34 | 35 | type RendezvousEntry = [server: T, hash: number]; 36 | function compareRendezvousEntries( 37 | e1: RendezvousEntry, 38 | e2: RendezvousEntry, 39 | ): number { 40 | return e1[1] - e2[1]; 41 | } 42 | -------------------------------------------------------------------------------- /docs/api/classes/jsonlogstream.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: JSONLogStream 3 | sidebar_label: JSONLogStream 4 | --- 5 | 6 | # JSONLogStream 7 | 8 | A LogStream implementation that writes log entries to a JSON file. 9 | Supports optional throttling to reduce the number of entries written. 10 | 11 | **Implements:** [LogStream](/api/interfaces/logstream) 12 | 13 | ## Constructor 14 | 15 | **new JSONLogStream(path: string, throttleRate: number)** 16 | 17 | Creates a new JSONLogStream 18 | 19 | ## Methods 20 | 21 | ### appendEntry() 22 | 23 | **appendEntry(e: NormalizedLogEntry<LogEntry>): Promise<void>** 24 | 25 | Appends a log entry to the JSON file, respecting the throttle rate 26 | 27 | ### close() 28 | 29 | **close(): Promise<void>** 30 | 31 | Closes the JSON log file if it's open. This is handled automatically by the 32 | stream and there's no need to call it explicitly under normal operation. 33 | 34 | You may want to call this explicitly when: 35 | - You need to ensure logs are flushed to disk immediately 36 | - You're shutting down the application and want to clean up resources 37 | - You want to release the file handle before the automatic timeout 38 | 39 | -------------------------------------------------------------------------------- /docs/docs/api/classes/jsonlogstream.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: JSONLogStream 3 | sidebar_label: JSONLogStream 4 | --- 5 | 6 | # JSONLogStream 7 | 8 | A LogStream implementation that writes log entries to a JSON file. 9 | Supports optional throttling to reduce the number of entries written. 10 | 11 | **Implements:** [LogStream](/docs/api/interfaces/logstream) 12 | 13 | ## Constructor 14 | 15 | **new JSONLogStream(path: string, throttleRate: number)** 16 | 17 | Creates a new JSONLogStream 18 | 19 | ## Methods 20 | 21 | ### appendEntry() 22 | 23 | **appendEntry(e: NormalizedLogEntry<LogEntry>): Promise<void>** 24 | 25 | Appends a log entry to the JSON file, respecting the throttle rate 26 | 27 | ### close() 28 | 29 | **close(): Promise<void>** 30 | 31 | Closes the JSON log file if it's open. This is handled automatically by the 32 | stream and there's no need to call it explicitly under normal operation. 33 | 34 | You may want to call this explicitly when: 35 | - You need to ensure logs are flushed to disk immediately 36 | - You're shutting down the application and want to clean up resources 37 | - You want to release the file handle before the automatic timeout 38 | 39 | -------------------------------------------------------------------------------- /external/MurmurHash3.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // MurmurHash3 was written by Austin Appleby, and is placed in the public 3 | // domain. The author hereby disclaims copyright to this source code. 4 | 5 | #ifndef _MURMURHASH3_H_ 6 | #define _MURMURHASH3_H_ 7 | 8 | //----------------------------------------------------------------------------- 9 | // Platform-specific functions and macros 10 | 11 | // Microsoft Visual Studio 12 | 13 | #if defined(_MSC_VER) && (_MSC_VER < 1600) 14 | 15 | typedef unsigned char uint8_t; 16 | typedef unsigned int uint32_t; 17 | typedef unsigned __int64 uint64_t; 18 | 19 | // Other compilers 20 | 21 | #else // defined(_MSC_VER) 22 | 23 | #include 24 | 25 | #endif // !defined(_MSC_VER) 26 | 27 | //----------------------------------------------------------------------------- 28 | 29 | void MurmurHash3_x86_32(const void* key, int len, uint32_t seed, void* out); 30 | 31 | void MurmurHash3_x86_128(const void* key, int len, uint32_t seed, void* out); 32 | 33 | void MurmurHash3_x64_128(const void* key, int len, uint32_t seed, void* out); 34 | 35 | //----------------------------------------------------------------------------- 36 | 37 | #endif // _MURMURHASH3_H_ -------------------------------------------------------------------------------- /base/tuple.ts: -------------------------------------------------------------------------------- 1 | export type Tuple4 = number; 2 | 3 | export type Tuple4Position = 0 | 1 | 2 | 3; 4 | 5 | export type Tuple4Values = [number, number, number, number]; 6 | 7 | export function tuple4Get(tuple: Tuple4, pos: Tuple4Position): number { 8 | return ((tuple & (255 << (pos * 8))) >> (pos * 8)) & 255; 9 | } 10 | 11 | export function tuple4Set( 12 | tuple: Tuple4, 13 | pos: Tuple4Position, 14 | value: number, 15 | ): Tuple4 { 16 | const mask = ~(255 << (pos * 8)); 17 | value &= 255; 18 | return (tuple & mask) | (value << (pos * 8)); 19 | } 20 | 21 | export function tuple4Make(values: Tuple4Values): Tuple4 { 22 | return ( 23 | (values[3] & 255) | 24 | ((values[2] & 255) << 8) | 25 | ((values[1] & 255) << 16) | 26 | ((values[0] & 255) << 24) 27 | ); 28 | } 29 | 30 | export function tuple4Break(tuple: Tuple4): Tuple4Values { 31 | return [ 32 | tuple4Get(tuple, 3), 33 | tuple4Get(tuple, 2), 34 | tuple4Get(tuple, 1), 35 | tuple4Get(tuple, 0), 36 | ]; 37 | } 38 | 39 | export function tuple4ToString(tuple: Tuple4): string { 40 | const lastValue = tuple4Get(tuple, 0); 41 | return `${tuple4Get(tuple, 3)}.${tuple4Get(tuple, 2)}.${tuple4Get(tuple, 1)}${ 42 | lastValue ? `-${lastValue}` : '' 43 | }`; 44 | } 45 | -------------------------------------------------------------------------------- /docs/src/components/icons/OfflineFirstIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function OfflineFirstIcon({ className }: { className?: string }) { 4 | return ( 5 | 11 | 21 | 27 | 33 | 40 | 47 | 48 | ); 49 | } -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "goatdb-docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "typecheck": "tsc" 10 | }, 11 | "dependencies": { 12 | "@docusaurus/core": "3.8.1", 13 | "@docusaurus/plugin-google-gtag": "^3.8.1", 14 | "@docusaurus/preset-classic": "3.8.1", 15 | "@docusaurus/remark-plugin-npm2yarn": "^3.8.1", 16 | "@docusaurus/theme-mermaid": "^3.8.1", 17 | "@mdx-js/react": "^3.0.0", 18 | "clsx": "^2.0.0", 19 | "prism-react-renderer": "^2.3.0", 20 | "react": "^19.0.0", 21 | "react-dom": "^19.0.0", 22 | "rehype-katex": "^7.0.1", 23 | "remark-math": "^6.0.0" 24 | }, 25 | "devDependencies": { 26 | "@docusaurus/module-type-aliases": "3.8.1", 27 | "@docusaurus/tsconfig": "3.8.1", 28 | "@docusaurus/types": "3.8.1", 29 | "typescript": "~5.6.2" 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.5%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 3 chrome version", 39 | "last 3 firefox version", 40 | "last 5 safari version" 41 | ] 42 | }, 43 | "engines": { 44 | "node": ">=18.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /server/mod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module exposes APIs for controlling the GoatDB server. It includes 3 | * functions for running the interactive debug server and for compiling a 4 | * standalone server executable. 5 | * 6 | * Check out https://goatdb.dev for additional docs. 7 | * 8 | * @module GoatDB/Server 9 | */ 10 | import { 11 | compile, 12 | type CompileOptions, 13 | type CPUArch, 14 | type ExecutableOptions, 15 | type TargetOS, 16 | } from '../cli/compile.ts'; 17 | import type { EmailBuilder, EmailInfo } from '../db/emails.ts'; 18 | import { staticAssetsFromJS } from '../system-assets/system-assets.ts'; 19 | import type { AppConfig } from './app-config.ts'; 20 | import { 21 | type DebugServerOptions, 22 | type LiveReloadOptions, 23 | startDebugServer, 24 | } from './debug-server.ts'; 25 | import { 26 | type DomainConfig, 27 | Server, 28 | type ServerOptions, 29 | type ServerServices, 30 | } from '../net/server/server.ts'; 31 | 32 | export type { 33 | AppConfig, 34 | CompileOptions, 35 | CPUArch, 36 | DebugServerOptions, 37 | DomainConfig, 38 | EmailBuilder, 39 | EmailInfo, 40 | ExecutableOptions, 41 | LiveReloadOptions, 42 | ServerOptions, 43 | ServerServices, 44 | TargetOS, 45 | }; 46 | export { compile, Server, startDebugServer, staticAssetsFromJS }; 47 | -------------------------------------------------------------------------------- /docs/src/components/icons/VersionHistoryIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function VersionHistoryIcon({ className }: { className?: string }) { 4 | return ( 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 30 | 36 | 37 | ); 38 | } -------------------------------------------------------------------------------- /cfds/change/index.ts: -------------------------------------------------------------------------------- 1 | import { assert } from '../../base/error.ts'; 2 | import { 3 | ConstructorDecoderConfig, 4 | isDecoderConfig, 5 | } from '../../base/core-types/encoding/index.ts'; 6 | import { 7 | Clonable, 8 | CoreValue, 9 | Encodable, 10 | Encoder, 11 | Equatable, 12 | ReadonlyCoreObject, 13 | } from '../../base/core-types/index.ts'; 14 | import { CoreValueCloneOpts } from '../../base/core-types/base.ts'; 15 | 16 | export type ChangeType = 'fd' | 'rt' | 'rt-2'; 17 | 18 | export interface EncodedChange extends ReadonlyCoreObject { 19 | readonly changeType: ChangeType; 20 | } 21 | 22 | export interface ChangeValueConfig {} 23 | 24 | export abstract class Change 25 | implements Encodable, Equatable, Clonable 26 | { 27 | constructor(config?: ChangeValueConfig | ConstructorDecoderConfig) { 28 | if (isDecoderConfig(config)) { 29 | assert(config.decoder.get('changeType') === this.getType()); 30 | } 31 | } 32 | 33 | abstract getType(): ChangeType; 34 | 35 | serialize(encoder: Encoder, _options?: unknown): void { 36 | encoder.set('changeType', this.getType()); 37 | } 38 | 39 | abstract isEqual(other: Change): boolean; 40 | 41 | abstract clone>(opts?: CoreValueCloneOpts): T; 42 | } 43 | -------------------------------------------------------------------------------- /net/server/cors.ts: -------------------------------------------------------------------------------- 1 | import type { Endpoint, Middleware, ServerServices } from './server.ts'; 2 | import type { Schema } from '../../cfds/base/schema.ts'; 3 | import { GoatRequest } from './http-compat.ts'; 4 | import type { ServeHandlerInfo } from './http-compat.ts'; 5 | 6 | export class CORSMiddleware implements Middleware { 7 | didProcess( 8 | services: ServerServices, 9 | req: GoatRequest, 10 | _info: ServeHandlerInfo, 11 | resp: Response, 12 | ): Promise { 13 | resp.headers.set( 14 | 'access-control-allow-origin', 15 | services.domain.resolveOrg(services.orgId), 16 | ); 17 | resp.headers.set('access-control-allow-methods', req.method || '*'); 18 | resp.headers.set('access-control-allow-headers', '*'); 19 | return Promise.resolve(resp); 20 | } 21 | } 22 | 23 | export class CORSEndpoint implements Endpoint { 24 | filter( 25 | _services: ServerServices, 26 | req: GoatRequest, 27 | _info: ServeHandlerInfo, 28 | ): boolean { 29 | return req.method === 'OPTIONS'; 30 | } 31 | 32 | processRequest( 33 | _services: ServerServices, 34 | _req: GoatRequest, 35 | _info: ServeHandlerInfo, 36 | ): Promise { 37 | return Promise.resolve(new Response(null)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /base/async.ts: -------------------------------------------------------------------------------- 1 | export interface SuccessResult { 2 | status: 'success'; 3 | result: T; 4 | } 5 | 6 | export interface ErrorResult { 7 | status: 'error'; 8 | error: any; 9 | } 10 | 11 | export type PromiseResult = SuccessResult | ErrorResult; 12 | 13 | /** 14 | * Given an array of promises, awaits on all of them and returns their results. 15 | * This method returns an array of result objects in the form of { result: res } 16 | * on success or { error: err } on error. Indexes in the result array are 17 | * guaranteed to match the indexes of the input array. 18 | */ 19 | export async function awaitPromises( 20 | promises: Promise[], 21 | returnResults: boolean = true 22 | ): Promise[]> { 23 | if (!promises.length) { 24 | return Promise.resolve([]); 25 | } 26 | 27 | const res = await Promise.all( 28 | promises.map(async (p) => { 29 | try { 30 | const res = await p; 31 | if (returnResults) { 32 | return { status: 'success', result: res }; 33 | } 34 | } catch (err) { 35 | if (returnResults) { 36 | return { status: 'error', error: err }; 37 | } 38 | } 39 | }) 40 | ); 41 | 42 | if (returnResults) { 43 | return res.filter((x) => x !== undefined); 44 | } 45 | 46 | return res; 47 | } 48 | -------------------------------------------------------------------------------- /net/server/health.ts: -------------------------------------------------------------------------------- 1 | import type { Endpoint, ServerServices } from './server.ts'; 2 | import type { Schema } from '../../cfds/base/schema.ts'; 3 | import type { GoatRequest, ServeHandlerInfo } from './http-compat.ts'; 4 | 5 | /** 6 | * Endpoint that handles health check requests. 7 | * 8 | * This endpoint responds to GET requests at /healthy with a 200 OK response 9 | * to indicate the server is running and healthy. It's commonly used by 10 | * load balancers and monitoring systems to verify server availability. 11 | * 12 | * @template US - The user schema type 13 | */ 14 | export class HealthCheckEndpoint implements Endpoint { 15 | filter( 16 | _server: ServerServices, 17 | req: GoatRequest, 18 | _info: ServeHandlerInfo, 19 | ): boolean { 20 | if (req.method !== 'GET') { 21 | return false; 22 | } 23 | const path = new URL(req.url).pathname.toLowerCase(); 24 | return path === '/healthy'; 25 | } 26 | 27 | processRequest( 28 | _server: ServerServices, 29 | _req: GoatRequest, 30 | _info: ServeHandlerInfo, 31 | ): Promise { 32 | return Promise.resolve( 33 | new Response('OK', { 34 | status: 200, 35 | headers: { 36 | 'Cache-control': 'no-store', 37 | }, 38 | }), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /server/config.ts: -------------------------------------------------------------------------------- 1 | import { isBrowser } from '../base/common.ts'; 2 | import { assert } from '../base/error.ts'; 3 | import { VCurrent, type VersionNumber } from '../base/version-number.ts'; 4 | 5 | export interface GoatConfig { 6 | version: VersionNumber; 7 | debug: boolean; 8 | orgId: string; 9 | clientData?: unknown; 10 | serverURL?: string; 11 | serverData?: unknown; 12 | } 13 | 14 | // deno-lint-ignore no-var 15 | // deno-lint-ignore no-unused-vars 16 | var GoatDBConfig: GoatConfig | undefined = undefined; 17 | 18 | type GlobalThis = typeof globalThis & { 19 | GoatDBConfig: GoatConfig | undefined; 20 | }; 21 | 22 | export function getGoatConfig(): GoatConfig { 23 | let config = (globalThis as GlobalThis).GoatDBConfig as 24 | | GoatConfig 25 | | undefined; 26 | if (!config) { 27 | assert(!isBrowser() || config !== undefined, 'GoatDBConfig not found'); 28 | config = config || { 29 | version: VCurrent, 30 | debug: false, 31 | orgId: 'localhost', 32 | }; 33 | (globalThis as GlobalThis).GoatDBConfig = config; 34 | } 35 | return config; 36 | } 37 | 38 | export function getClientData(): T | undefined { 39 | return getGoatConfig().clientData as T; 40 | } 41 | 42 | export function setClientData(data: T | undefined): void { 43 | getGoatConfig().clientData = data; 44 | } 45 | -------------------------------------------------------------------------------- /docs/src/components/icons/SingleTenantIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function SingleTenantIcon({ className }: { className?: string }) { 4 | return ( 5 | 11 | 21 | 31 | 41 | 51 | 57 | 58 | ); 59 | } -------------------------------------------------------------------------------- /tests/orderstamp-expose.test.ts: -------------------------------------------------------------------------------- 1 | import { assertTrue } from './asserts.ts'; 2 | import { TEST } from './mod.ts'; 3 | import { Orderstamp } from '../mod.ts'; 4 | 5 | export default function setup(): void { 6 | TEST('Orderstamp', 'Orderstamp API is exposed', () => { 7 | assertTrue(typeof Orderstamp === 'object'); 8 | assertTrue(typeof Orderstamp.start === 'function'); 9 | assertTrue(typeof Orderstamp.end === 'function'); 10 | assertTrue(typeof Orderstamp.from === 'function'); 11 | assertTrue(typeof Orderstamp.between === 'function'); 12 | }); 13 | 14 | TEST('Orderstamp', 'Orderstamp basic usage', () => { 15 | const s = Orderstamp.start(); 16 | const e = Orderstamp.end(); 17 | const f = Orderstamp.from(42); 18 | const b = Orderstamp.between(s, e); 19 | assertTrue(typeof s === 'string'); 20 | assertTrue(typeof e === 'string'); 21 | assertTrue(typeof f === 'string'); 22 | assertTrue(typeof b === 'string'); 23 | assertTrue(s < e); 24 | assertTrue(s < b && b < e); 25 | }); 26 | 27 | TEST('Orderstamp', 'Orderstamp between is monotonic', () => { 28 | const s = Orderstamp.start(); 29 | const e = Orderstamp.end(); 30 | const b1 = Orderstamp.between(s, e); 31 | const b2 = Orderstamp.between(s, b1); 32 | const b3 = Orderstamp.between(b1, e); 33 | assertTrue(s < b2 && b2 < b1 && b1 < b3 && b3 < e); 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /base/core-types/encoding/types.ts: -------------------------------------------------------------------------------- 1 | import { ReadonlyJSONValue } from '../../interfaces.ts'; 2 | 3 | export type ReadonlyDecodedObject = { 4 | readonly [key: string]: DecodedValue; 5 | }; 6 | 7 | export type DecodableKey = string; 8 | 9 | export type ReadonlyDecodedArray = readonly DecodedValue[]; 10 | 11 | export type DecodedValue = 12 | | ReadonlyJSONValue 13 | | Date 14 | | Set 15 | | ReadonlyDecodedObject 16 | | ReadonlyDecodedArray 17 | | Decoder; 18 | 19 | export interface Decoder< 20 | K extends DecodableKey = DecodableKey, 21 | V extends DecodedValue = DecodedValue, 22 | > { 23 | get(key: K, defaultValue?: T): T | typeof defaultValue; 24 | has(key: K): boolean; 25 | getDecoder(key: K, offset?: number): Decoder; 26 | } 27 | 28 | export function isDecoder< 29 | K extends DecodableKey = DecodableKey, 30 | V extends DecodedValue = DecodedValue, 31 | >(v: DecodedValue): v is Decoder { 32 | return typeof (v as Decoder).getDecoder === 'function'; 33 | } 34 | 35 | export interface Decodable< 36 | K extends DecodableKey = DecodableKey, 37 | V extends DecodedValue = DecodedValue, 38 | OT = unknown, 39 | > { 40 | deserialize(decoder: Decoder, options?: OT): void; 41 | } 42 | 43 | export interface ConstructorDecoderConfig { 44 | readonly decoder: Decoder; 45 | } 46 | -------------------------------------------------------------------------------- /docs/src/components/icons/MultiplayerIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function MultiplayerIcon({ className }: { className?: string }) { 4 | return ( 5 | 11 | 21 | 29 | 37 | 43 | 49 | 55 | 56 | ); 57 | } -------------------------------------------------------------------------------- /base/coroutine-timer.ts: -------------------------------------------------------------------------------- 1 | import { Scheduler, SchedulerPriority } from './coroutine.ts'; 2 | import { Timer, TimerCallback } from './timer.ts'; 3 | 4 | /** 5 | * An coroutine based implementation of a NextEventLoopCycleTimer. This timer 6 | * is managed by a CoroutineScheduler, increasing processing responsiveness. 7 | * 8 | * Prefer to use this timer over NextEventLoopCycleTimer whenever possible. 9 | */ 10 | export class CoroutineTimer implements Timer { 11 | private readonly _callback: TimerCallback; 12 | private _scheduled: boolean; 13 | 14 | constructor( 15 | readonly scheduler: Scheduler, 16 | callback: TimerCallback, 17 | readonly priority: SchedulerPriority = SchedulerPriority.Normal, 18 | readonly name?: string 19 | ) { 20 | this._callback = callback; 21 | this._scheduled = false; 22 | } 23 | 24 | schedule(): Timer { 25 | if (!this._scheduled) { 26 | this._scheduled = true; 27 | this.scheduler.schedule(this._run(), this.priority, this.name); 28 | } 29 | return this; 30 | } 31 | 32 | unschedule(): Timer { 33 | this._scheduled = false; 34 | return this; 35 | } 36 | 37 | private *_run(): Generator { 38 | const callback = this._callback; 39 | while (true) { 40 | if (!this._scheduled || callback(this) !== true) { 41 | this._scheduled = false; 42 | return; 43 | } 44 | yield; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /cfds/base/types/primitive-type.ts: -------------------------------------------------------------------------------- 1 | import { valueTypeEquals, ValueTypeOptions } from './index.ts'; 2 | import { CoreTypeOperations } from './core-type.ts'; 3 | import { Change, EncodedChange } from '../../change/index.ts'; 4 | import { FieldChange, FieldOperation } from '../../change/field-change.ts'; 5 | import { ConcreteCoreValue } from '../../../base/core-types/index.ts'; 6 | 7 | export class PrimitiveTypeOperations< 8 | TValue extends ConcreteCoreValue 9 | > extends CoreTypeOperations { 10 | patch( 11 | curValue: TValue | undefined, 12 | changes: Change[], 13 | options?: ValueTypeOptions 14 | ): TValue | undefined { 15 | for (const change of changes) { 16 | if (change instanceof FieldChange) { 17 | if (change.operation === FieldOperation.Insert) { 18 | curValue = change.value; 19 | } else if (change.operation === FieldOperation.Delete) { 20 | curValue = valueTypeEquals(change.valueType, curValue, change.value) 21 | ? undefined 22 | : curValue; 23 | } 24 | } 25 | } 26 | 27 | return curValue; 28 | } 29 | 30 | valueChangedDiff(value1: TValue, value2: TValue, options?: ValueTypeOptions) { 31 | if (!this.equals(value1!, value2!, options)) { 32 | return FieldChange.insert(value2, this.valueType); 33 | } 34 | } 35 | 36 | isEmpty(value: TValue) { 37 | return false; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/browser/debug-server-entry.ts: -------------------------------------------------------------------------------- 1 | import * as path from '@std/path'; 2 | import { startDebugServer } from '../../server/debug-server.ts'; 3 | import type { Schema } from '../../cfds/base/schema.ts'; 4 | import { FileImplGet } from '../../base/json-log/file-impl.ts'; 5 | import { exit } from '../../base/process.ts'; 6 | import { getEnvVar } from '../../base/os.ts'; 7 | 8 | /** 9 | * Entry point for browser test debug server. 10 | * This starts an HTTPS debug server specifically for browser testing. 11 | */ 12 | async function browserTestsServerMain() { 13 | try { 14 | console.log('Starting HTTPS debug server for browser tests...'); 15 | await startDebugServer({ 16 | path: path.join( 17 | await (await FileImplGet()).getTempDir(), 18 | 'browser-test-data', 19 | ), 20 | buildDir: './build', 21 | jsPath: './tests/tests-entry-browser.ts', 22 | htmlPath: './tests/browser/test-runner.html', 23 | port: 8080, 24 | orgId: 'browser-test-org', 25 | https: { selfSigned: true }, 26 | watchDir: '.', 27 | customConfig: { 28 | testMode: true, 29 | suite: getEnvVar('GOATDB_SUITE'), 30 | test: getEnvVar('GOATDB_TEST'), 31 | }, 32 | }); 33 | } catch (error) { 34 | console.error('Failed to start debug server:', error); 35 | exit(1); 36 | } 37 | } 38 | 39 | if (import.meta.main) { 40 | browserTestsServerMain(); 41 | } 42 | -------------------------------------------------------------------------------- /docs/src/components/diagrams/base/Arrow.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from '../diagrams.module.css'; 3 | 4 | interface ArrowProps { 5 | direction?: 'horizontal' | 'vertical' | 'bidirectional'; 6 | animated?: boolean; 7 | label?: string; 8 | className?: string; 9 | } 10 | 11 | export default function Arrow({ 12 | direction = 'horizontal', 13 | animated = false, 14 | label, 15 | className = '' 16 | }: ArrowProps) { 17 | return ( 18 | 42 | ); 43 | } -------------------------------------------------------------------------------- /docs/src/components/icons/CollaborativeIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function CollaborativeIcon({ className }: { className?: string }) { 4 | return ( 5 | 11 | 21 | 27 | 33 | 39 | 46 | 53 | 54 | ); 55 | } -------------------------------------------------------------------------------- /base/math.ts: -------------------------------------------------------------------------------- 1 | export interface NumberAggregator { 2 | get currentValue(): number; 3 | addValue(v: number): void; 4 | } 5 | 6 | export function avg(values: Iterable): number { 7 | let result = 0; 8 | let count = 0; 9 | for (const v of values) { 10 | result += v; 11 | ++count; 12 | } 13 | return count === 0 ? 0 : result / count; 14 | } 15 | 16 | export class MovingAverage implements NumberAggregator { 17 | private readonly _readingsCount: number; 18 | private _values: number[]; 19 | 20 | constructor(readingsCount: number) { 21 | this._readingsCount = readingsCount; 22 | this._values = []; 23 | } 24 | 25 | get currentValue(): number { 26 | return avg(this._values); 27 | } 28 | 29 | addValue(v: number): void { 30 | const values = this._values; 31 | if (values.length >= this._readingsCount) { 32 | values.shift(); 33 | } 34 | values.push(v); 35 | } 36 | } 37 | 38 | // min - inclusive, max - exclusive 39 | // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random#Getting_a_random_integer_between_two_values 40 | export function randomInt(min: number, max: number): number { 41 | if (min === max) return min; 42 | 43 | min = Math.ceil(min); 44 | max = Math.floor(max); 45 | return Math.floor(Math.random() * (max - min)) + min; 46 | } 47 | 48 | export type Point2D = { x: number; y: number }; 49 | export type Size2D = { width: number; height: number }; 50 | export type Rect2D = Size2D & Point2D; 51 | -------------------------------------------------------------------------------- /benchmarks/browser/debug-server-entry.ts: -------------------------------------------------------------------------------- 1 | import * as path from '@std/path'; 2 | import { startDebugServer } from '../../server/debug-server.ts'; 3 | import type { Schema } from '../../cfds/base/schema.ts'; 4 | import { FileImplGet } from '../../base/json-log/file-impl.ts'; 5 | import { exit } from '../../base/process.ts'; 6 | import { getEnvVar } from '../../base/os.ts'; 7 | 8 | /** 9 | * Entry point for browser benchmark debug server. 10 | * This starts an HTTPS debug server specifically for browser benchmarking. 11 | */ 12 | async function browserBenchmarksServerMain() { 13 | try { 14 | console.log('Starting HTTPS debug server for browser benchmarks...'); 15 | await startDebugServer({ 16 | path: path.join( 17 | await (await FileImplGet()).getTempDir(), 18 | 'browser-benchmark-data', 19 | ), 20 | buildDir: './build', 21 | jsPath: './benchmarks/benchmarks-entry-browser.ts', 22 | htmlPath: './benchmarks/browser/benchmark-runner.html', 23 | assetsPath: './benchmarks/browser/assets', 24 | port: 8080, 25 | orgId: 'browser-benchmark-org', 26 | https: { selfSigned: true }, 27 | watchDir: '.', 28 | customConfig: { 29 | benchmarkMode: true, 30 | benchmark: getEnvVar('GOATDB_BENCHMARK'), 31 | }, 32 | }); 33 | } catch (error) { 34 | console.error('Failed to start debug server:', error); 35 | exit(1); 36 | } 37 | } 38 | 39 | if (import.meta.main) { 40 | browserBenchmarksServerMain(); 41 | } -------------------------------------------------------------------------------- /docs/docs/api/interfaces/serverservices.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: ServerServices 3 | sidebar_label: ServerServices 4 | --- 5 | 6 | # ServerServices 7 | 8 | The services object is accessible to all endpoints and middlewares of the 9 | server. Several services instances exist in the server. First a base instance 10 | corresponding to the root level of the server's data dir. Second, an 11 | organization specific services and DB instance is created inside the root 12 | data dir so all organization data is contained in a single DB/directory on 13 | disk. 14 | 15 | **Extends:** [ServerOptions](/docs/api/interfaces/serveroptions) 16 | 17 | ## Properties 18 | 19 | ### db 20 | 21 | ```typescript 22 | db: [GoatDB](/docs/api/classes/goatdb) 23 | ``` 24 | 25 | ### email 26 | 27 | ```typescript 28 | email: EmailService 29 | ``` 30 | 31 | ### logger 32 | 33 | ```typescript 34 | logger: Logger 35 | ``` 36 | 37 | ### orgId 38 | 39 | ```typescript 40 | orgId: string 41 | ``` 42 | 43 | Optional organization id used to sandbox the data of a specific 44 | organization in a multi-tenant deployment. Defaults to "localhost". 45 | 46 | ## Inherited Members 47 | 48 | ### From [ServerOptions](/docs/api/interfaces/serveroptions) 49 | 50 | **Properties:** `appName`, `buildInfo`, `customConfig`, `debug`, `denoJson`, `disableDefaultEndpoints`, `domain`, `emailConfig`, `fetchUserByEmail`, `https`, `logStreams`, `mode`, `path`, `peers`, `port`, `registry`, `staticAssets`, `trusted` 51 | 52 | *See [ServerOptions](/docs/api/interfaces/serveroptions) for detailed documentation* 53 | 54 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # GoatDB Documentation 2 | 3 | This documentation site is built using [Docusaurus](https://docusaurus.io/), a modern static website generator. 4 | 5 | ## Prerequisites 6 | 7 | - Deno (no Node.js or npm required!) 8 | 9 | ## Installation 10 | 11 | First time setup - install dependencies using Deno: 12 | 13 | ```bash 14 | cd docs 15 | deno install 16 | ``` 17 | 18 | This will download and cache all npm dependencies specified in package.json using Deno's npm compatibility layer. 19 | 20 | ## Development 21 | 22 | To run the documentation site locally, use the Deno build script from the project root: 23 | 24 | ```bash 25 | deno run -A docs-build.ts serve 26 | ``` 27 | 28 | This will start a local development server with hot reload at http://localhost:3000. 29 | 30 | ## Build 31 | 32 | To build the static documentation site: 33 | 34 | ```bash 35 | deno run -A docs-build.ts build 36 | ``` 37 | 38 | This generates static content into the `build/docs` directory. 39 | 40 | ## Architecture 41 | 42 | GoatDB uses Deno throughout the entire project, including for documentation. The `docs-build.ts` script leverages Deno's npm compatibility to run Docusaurus directly without requiring Node.js: 43 | 44 | - Uses `deno run -A npm:@docusaurus/core` to execute Docusaurus commands 45 | - Dependencies are managed through `package.json` but executed via Deno 46 | - No `node_modules` directory or npm installation required 47 | 48 | This approach maintains consistency with GoatDB's Deno-first philosophy while leveraging the excellent documentation features of Docusaurus. -------------------------------------------------------------------------------- /system-assets/system-assets.ts: -------------------------------------------------------------------------------- 1 | import { encodeBase64 } from '@std/encoding/base64'; 2 | import { decodeBase64 } from '../base/buffer.ts'; 3 | import type { JSONObject, ReadonlyJSONObject } from '../base/interfaces.ts'; 4 | import kEncodedSystemAssets from './assets.json' with { 5 | type: 'json', 6 | }; 7 | 8 | export type ContentType = 9 | | 'image/svg+xml' 10 | | 'image/png' 11 | | 'image/jpeg' 12 | | 'image/jpeg' 13 | | 'application/json' 14 | | 'text/javascript' 15 | | 'text/html' 16 | | 'text/css' 17 | | 'application/wasm'; 18 | 19 | export interface Asset { 20 | data: Uint8Array; 21 | contentType: ContentType; 22 | } 23 | 24 | export type StaticAssets = Record; 25 | 26 | export function staticAssetsToJS(assets: StaticAssets): ReadonlyJSONObject { 27 | const result: JSONObject = {}; 28 | for (const [path, asset] of Object.entries(assets)) { 29 | result[path] = { 30 | data: encodeBase64(asset.data), 31 | contentType: asset.contentType, 32 | }; 33 | } 34 | return result; 35 | } 36 | 37 | export function staticAssetsFromJS( 38 | encodedAssets: ReadonlyJSONObject, 39 | ): StaticAssets { 40 | const result: StaticAssets = {}; 41 | for (const [path, asset] of Object.entries(encodedAssets)) { 42 | result[path] = { 43 | data: decodeBase64((asset as ReadonlyJSONObject).data as string), 44 | contentType: (asset as ReadonlyJSONObject).contentType as ContentType, 45 | }; 46 | } 47 | return result as StaticAssets; 48 | } 49 | 50 | export const kStaticAssetsSystem = staticAssetsFromJS(kEncodedSystemAssets); 51 | -------------------------------------------------------------------------------- /docs/docs/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: installation 3 | title: Install 4 | sidebar_position: 0 5 | slug: /install 6 | --- 7 | 8 | 9 | ## GoatDB Installation 10 | 11 | GoatDB can be installed in both Deno and Node.js environments. Deno is our 12 | preferred runtime (with benefits like compiling to a self-contained executable), 13 | while Node.js support is currently **experimental**. 14 | 15 | ### Deno Installation (Recommended) 16 | 17 | 1. **Add GoatDB to your project:** 18 | 19 | ```bash 20 | deno add jsr:@goatdb/goatdb 21 | ``` 22 | 23 | 2. **Initialize the React Scaffold** (optional, for SPAs only): 24 | 25 | ```bash 26 | deno run -A jsr:@goatdb/goatdb/init 27 | ``` 28 | > **Note**: The initialization step is only required for Single Page 29 | > Applications (SPAs). This command installs React dependencies and creates a 30 | > project scaffold with both client-side and server-side code structures. 31 | > Skip this step if you're not building a SPA or already have your React 32 | > setup configured. 33 | 34 | ### Node.js Installation 35 | 36 | Install using one of the following package managers: 37 | 38 | :::warning 39 | 40 | Node.js support is currently a work in progress and is not yet ready for 41 | production. Progress is tracked under this 42 | [GitHub issue](https://github.com/goatplatform/goatdb/issues/27). 43 | 44 | ::: 45 | 46 | **npm:** 47 | 48 | ```bash 49 | npx jsr add @goatdb/goatdb 50 | ``` 51 | 52 | **Yarn:** 53 | 54 | ```bash 55 | yarn dlx jsr add @goatdb/goatdb 56 | ``` 57 | 58 | **pnpm:** 59 | 60 | ```bash 61 | pnpm dlx jsr add @goatdb/goatdb 62 | ``` 63 | -------------------------------------------------------------------------------- /base/collections/queue.ts: -------------------------------------------------------------------------------- 1 | import { bsearch } from '../algorithms.ts'; 2 | 3 | function defaultComparator(a: T, b: T): number { 4 | return (a as any) - (b as any); 5 | } 6 | 7 | function defaultEQ(a: T, b: T): boolean { 8 | return a === b; 9 | } 10 | 11 | export class SortedQueue { 12 | readonly _arr: T[]; // TODO: Use a binary tree 13 | readonly _comparator: (x: T, y: T) => number; 14 | readonly _eq: (a: T, y: T) => boolean; 15 | 16 | constructor( 17 | comparator?: (x: T, y: T) => number, 18 | eq?: (a: T, y: T) => boolean 19 | ) { 20 | this._arr = []; 21 | this._comparator = comparator || defaultComparator; 22 | this._eq = eq || defaultEQ; 23 | } 24 | 25 | get size(): number { 26 | return this._arr.length; 27 | } 28 | 29 | get peek(): T | undefined { 30 | return this._arr[this._arr.length - 1]; 31 | } 32 | 33 | push(v: T): void { 34 | const arr = this._arr; 35 | const idx = bsearch(arr, v, this._comparator); 36 | if (idx >= arr.length || !this._eq(arr[idx], v)) { 37 | this._arr.splice(idx, 0, v); 38 | } 39 | } 40 | 41 | pop(): T | undefined { 42 | return this._arr.pop(); 43 | } 44 | 45 | delete(v: T): boolean { 46 | const arr = this._arr; 47 | const idx = bsearch(arr, v, this._comparator); 48 | if (idx < arr.length && this._eq(arr[idx], v)) { 49 | arr.splice(idx, 1); 50 | return true; 51 | } 52 | return false; 53 | } 54 | 55 | has(v: T): boolean { 56 | const arr = this._arr; 57 | const idx = bsearch(arr, v, this._comparator); 58 | return idx < arr.length && this._eq(arr[idx], v); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /cfds/base/listenable.ts: -------------------------------------------------------------------------------- 1 | export interface Listener { 2 | (value: T): void; 3 | } 4 | 5 | export abstract class Listenable { 6 | private _listeners: Listener[]; 7 | 8 | constructor() { 9 | this._listeners = []; 10 | } 11 | 12 | protected get hasListeners() { 13 | return this._listeners.length > 0; 14 | } 15 | 16 | protected beforeFirstListener(): void {} 17 | 18 | protected abstract triggerListener(listener: Listener): void; 19 | 20 | protected afterLastListener(): void {} 21 | 22 | notify() { 23 | for (const f of this._listeners) { 24 | this.triggerListener(f); 25 | } 26 | } 27 | 28 | listen(f: (value: T) => void, fireOnStart: boolean = true) { 29 | if (this._listeners.length === 0) { 30 | this.beforeFirstListener(); 31 | } 32 | this._listeners.push(f); 33 | if (fireOnStart) { 34 | if (typeof window !== 'undefined') { 35 | setTimeout(() => { 36 | if (this._listeners.indexOf(f) !== -1) { 37 | this.triggerListener(f); 38 | } 39 | }, 0); 40 | } else { 41 | setTimeout(() => { 42 | if (this._listeners.indexOf(f) !== -1) { 43 | this.triggerListener(f); 44 | } 45 | }, 0); 46 | } 47 | } 48 | return () => { 49 | const idx = this._listeners.indexOf(f); 50 | if (idx >= 0) { 51 | this._listeners.splice(idx, 1); 52 | } 53 | if (!this._listeners.length) { 54 | this.afterLastListener(); 55 | } 56 | }; 57 | } 58 | 59 | removeAllListeners() { 60 | this._listeners = []; 61 | this.afterLastListener(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /docs/api/classes/trustpool.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: TrustPool 3 | sidebar_label: TrustPool 4 | --- 5 | 6 | # TrustPool 7 | 8 | Each commit in the graph is signed with the private key of the session that 9 | generated it. The trust pool manages all known sessions and their public keys 10 | which are later used to verify that the commits in the graph make only 11 | modifications that are valid. Each commit's signature is checked against its 12 | session's public key, and then the modifications themselves are checked to 13 | ensure the creator only made changes within their scope of permissions. 14 | 15 | ## Constructor 16 | 17 | **new TrustPool(orgId: string, currentSession: OwnedSession, roots: Session[], trustedSessions: Session[], changeCallback: () => void)** 18 | 19 | ## Methods 20 | 21 | ### addSession() 22 | 23 | **addSession(s: Session, commit: Commit<>): Promise<boolean>** 24 | 25 | When a commit containing a session is discovered, use this method to add 26 | it to the trust pool. This method does the necessary checks to ensure the 27 | integrity of this commit, then if all checks pass it adds the session to 28 | the trust pool. 29 | 30 | ### addSessionUnsafe() 31 | 32 | **addSessionUnsafe(s: Session): boolean** 33 | 34 | Adds the given session to the trust pool, without verifying its origin 35 | commit. Be very careful to only use this method for verified sessions. 36 | 37 | ### getSession() 38 | 39 | **getSession(id: string): Session** 40 | 41 | ### sessions() 42 | 43 | **sessions(): Generator<Session>** 44 | 45 | ### verify() 46 | 47 | **verify(commit: Commit<>): Promise<boolean>** 48 | 49 | -------------------------------------------------------------------------------- /base/version-number.ts: -------------------------------------------------------------------------------- 1 | import { Tuple4, tuple4Make } from './tuple.ts'; 2 | 3 | export type VersionNumber = Tuple4; 4 | 5 | // 21/1/2024 6 | export const V3_0_0: VersionNumber = tuple4Make([3, 0, 0, 0]); 7 | // 25/1/2024 8 | export const V3_0_1: VersionNumber = tuple4Make([3, 0, 1, 0]); 9 | // 1/2/2024 10 | export const V3_0_2: VersionNumber = tuple4Make([3, 0, 2, 0]); 11 | // 8/2/2024 12 | export const V3_1_0: VersionNumber = tuple4Make([3, 1, 0, 0]); 13 | // 11/2/2024 14 | export const V3_1_1: VersionNumber = tuple4Make([3, 1, 1, 0]); 15 | // 14/2/2024 16 | export const V3_1_2: VersionNumber = tuple4Make([3, 1, 2, 0]); 17 | // 15/2/2024 18 | export const V3_1_3: VersionNumber = tuple4Make([3, 1, 3, 0]); 19 | // 19/2/2024 20 | export const V3_1_4: VersionNumber = tuple4Make([3, 1, 4, 0]); 21 | // 26/2/2024 22 | export const V3_2_0: VersionNumber = tuple4Make([3, 2, 0, 0]); 23 | // 17/3/2024 24 | export const V3_3_0: VersionNumber = tuple4Make([3, 3, 0, 0]); 25 | // 19/3/2024 26 | export const V3_4_0: VersionNumber = tuple4Make([3, 4, 0, 0]); 27 | // 26/3/2024 28 | export const V3_4_1: VersionNumber = tuple4Make([3, 4, 1, 0]); 29 | // 17/4/2024 30 | export const V3_4_2: VersionNumber = tuple4Make([3, 4, 2, 0]); 31 | // 18/4/2024 32 | export const V3_4_3: VersionNumber = tuple4Make([3, 4, 3, 0]); 33 | // 5/5/2024 34 | export const V3_4_4: VersionNumber = tuple4Make([3, 4, 4, 0]); 35 | // 9/5/2024 36 | export const V3_4_5: VersionNumber = tuple4Make([3, 4, 5, 0]); 37 | // 21/5/2024 38 | export const V3_4_6: VersionNumber = tuple4Make([3, 4, 6, 0]); 39 | export const V3_4_7: VersionNumber = tuple4Make([3, 4, 7, 0]); 40 | export const V3_5_0: VersionNumber = tuple4Make([3, 5, 0, 0]); 41 | 42 | export const VCurrent = V3_4_7; 43 | -------------------------------------------------------------------------------- /docs/docs/api/classes/trustpool.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: TrustPool 3 | sidebar_label: TrustPool 4 | --- 5 | 6 | # TrustPool 7 | 8 | Each commit in the graph is signed with the private key of the session that 9 | generated it. The trust pool manages all known sessions and their public keys 10 | which are later used to verify that the commits in the graph make only 11 | modifications that are valid. Each commit's signature is checked against its 12 | session's public key, and then the modifications themselves are checked to 13 | ensure the creator only made changes within their scope of permissions. 14 | 15 | ## Constructor 16 | 17 | **new TrustPool(orgId: string, currentSession: OwnedSession, roots: Session[], trustedSessions: Session[], changeCallback: () => void)** 18 | 19 | ## Methods 20 | 21 | ### addSession() 22 | 23 | **addSession(s: Session, commit: Commit<>): Promise<boolean>** 24 | 25 | When a commit containing a session is discovered, use this method to add 26 | it to the trust pool. This method does the necessary checks to ensure the 27 | integrity of this commit, then if all checks pass it adds the session to 28 | the trust pool. 29 | 30 | ### addSessionUnsafe() 31 | 32 | **addSessionUnsafe(s: Session): boolean** 33 | 34 | Adds the given session to the trust pool, without verifying its origin 35 | commit. Be very careful to only use this method for verified sessions. 36 | 37 | ### getSession() 38 | 39 | **getSession(id: string): Session** 40 | 41 | ### sessions() 42 | 43 | **sessions(): Generator<Session>** 44 | 45 | ### verify() 46 | 47 | **verify(commit: Commit<>): Promise<boolean>** 48 | 49 | -------------------------------------------------------------------------------- /examples/nodejs-basic/README.md: -------------------------------------------------------------------------------- 1 | # GoatDB Node.js Basic Example 2 | 3 | A simple example showing GoatDB usage in Node.js with CRUD operations, schemas, and reactive queries. 4 | 5 | ## Installation & Running 6 | 7 | ```bash 8 | npm install 9 | npm start 10 | ``` 11 | 12 | ## What it demonstrates 13 | 14 | ```javascript 15 | import { GoatDB, DataRegistry } from '@goatdb/goatdb'; 16 | 17 | // 1. Define and register schema 18 | const taskSchema = { 19 | ns: 'task', 20 | version: 1, 21 | fields: { 22 | text: { type: 'string', required: true }, 23 | done: { type: 'boolean', default: () => false } 24 | } 25 | }; 26 | DataRegistry.default.registerSchema(taskSchema); 27 | 28 | // 2. Initialize database 29 | const db = new GoatDB({ path: './data' }); 30 | await db.readyPromise(); 31 | 32 | // 3. Create items 33 | const task = db.create('/data/todos/task-1', taskSchema, { 34 | text: 'Learn GoatDB' 35 | }); 36 | 37 | // 4. Query with predicates 38 | const incompleteTasks = db.query({ 39 | source: '/data/todos', 40 | schema: taskSchema, 41 | predicate: ({ item }) => !item.get('done') 42 | }); 43 | 44 | // 5. Reactive updates 45 | incompleteTasks.onResultsChanged(() => { 46 | console.log('Tasks updated!'); 47 | }); 48 | 49 | // 6. Update items 50 | task.set('done', true); 51 | ``` 52 | 53 | ## Key concepts 54 | 55 | - **Path format**: `/type/repo/item` (e.g., `/data/todos/task-1`) 56 | - **Schema registration**: Required before creating items 57 | - **Reactive queries**: Automatically update when data changes 58 | - **Memory-first**: Fast operations with persistent storage 59 | - **Security mode**: Uses default security settings. For backend-only applications in secure environments, see [trusted mode documentation](https://goatdb.dev/sessions#trusted-mode) -------------------------------------------------------------------------------- /docs/api/interfaces/dbinstanceconfig.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: DBInstanceConfig 3 | sidebar_label: DBInstanceConfig 4 | --- 5 | 6 | # DBInstanceConfig 7 | 8 | ## Properties 9 | 10 | ### debug 11 | 12 | ```typescript 13 | debug: boolean 14 | ``` 15 | 16 | If true, the DB will be in debug mode. 17 | Defaults to false. 18 | 19 | ### mode 20 | 21 | ```typescript 22 | mode: DBMode 23 | ``` 24 | 25 | The mode of operation for this database instance. 26 | 27 | ### orgId 28 | 29 | ```typescript 30 | orgId: string 31 | ``` 32 | 33 | Optional organization id used to sandbox the data of a specific 34 | organization in a multi-tenant deployment. Defaults to "localhost". 35 | 36 | ### path 37 | 38 | ```typescript 39 | path: string 40 | ``` 41 | 42 | Absolute path to the directory that'll store the DB's data. 43 | 44 | ### peers 45 | 46 | ```typescript 47 | peers: string | Iterable 48 | ``` 49 | 50 | Absolute URLs of peers to sync with. Peers are must share the same 51 | public/private root keys of this instance. 52 | 53 | ### registry 54 | 55 | ```typescript 56 | registry: [DataRegistry](/api/classes/dataregistry)<> 57 | ``` 58 | 59 | Optional schema registry to use for this database instance. 60 | If not provided, the default global registry (DataRegistry.default) will 61 | be used. The registry contains all schema definitions and authorization 62 | rules that this database instance will work with. Authorization rules 63 | define who can read, write, or delete data based on user permissions and 64 | data properties. 65 | 66 | ### trusted 67 | 68 | ```typescript 69 | trusted: boolean 70 | ``` 71 | 72 | If true, all security mechanisms are bypassed in favor of speed. 73 | Set this to true when running purely in a trusted backend environment. 74 | Defaults to false. 75 | 76 | -------------------------------------------------------------------------------- /net/server/logs.ts: -------------------------------------------------------------------------------- 1 | import { Endpoint, ServerServices } from './server.ts'; 2 | import { getRequestPath } from './utils.ts'; 3 | import { requireSignedUser } from './auth.ts'; 4 | import { accessDenied } from '../../cfds/base/errors.ts'; 5 | 6 | export class LogsEndpoint implements Endpoint { 7 | filter( 8 | services: ServerServices, 9 | req: Request, 10 | info: Deno.ServeHandlerInfo 11 | ): boolean { 12 | if (req.method !== 'POST') { 13 | return false; 14 | } 15 | const path = getRequestPath(req); 16 | return path === '/logs/query'; 17 | } 18 | 19 | async processRequest( 20 | services: ServerServices, 21 | req: Request, 22 | info: Deno.ServeHandlerInfo 23 | ): Promise { 24 | if (!req.body) { 25 | return Promise.resolve( 26 | new Response(null, { 27 | status: 400, 28 | }) 29 | ); 30 | } 31 | 32 | const reqJson = await req.json(); 33 | const [userId, userRecord, userSession] = await requireSignedUser( 34 | services, 35 | req, 36 | 'operator' 37 | ); 38 | const db = services.sqliteLogStream.db; 39 | const query = reqJson.query; 40 | services.logger.log({ 41 | severity: 'METRIC', 42 | name: 'OperatorLogsQuery', 43 | value: 1, 44 | unit: 'Count', 45 | operator: userRecord?.get('email') || userId, 46 | session: userSession.id, 47 | query, 48 | }); 49 | const statement = db.prepare(query); 50 | if (!statement.readonly) { 51 | throw accessDenied(); 52 | } 53 | const results = statement.all(); 54 | return new Response(JSON.stringify(results), { 55 | headers: { 56 | 'content-type': 'application/json; charset=utf-8', 57 | }, 58 | }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /base/process-manager.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple process lifecycle manager for browser testing infrastructure. 3 | * Handles spawning and cleanup of child processes with graceful shutdown. 4 | */ 5 | 6 | export class ProcessManager { 7 | private processes = new Set(); 8 | 9 | /** 10 | * Spawn a new child process and track it for cleanup. 11 | */ 12 | spawn(command: string, args: string[], options?: Deno.CommandOptions): Deno.ChildProcess { 13 | const process = new Deno.Command(command, { args, ...options }).spawn(); 14 | this.processes.add(process); 15 | 16 | // Auto-cleanup when process exits naturally 17 | process.status.finally(() => this.processes.delete(process)); 18 | return process; 19 | } 20 | 21 | /** 22 | * Clean up all tracked processes with graceful shutdown. 23 | * Sends SIGTERM, waits, then SIGKILL if needed. 24 | */ 25 | async cleanup(gracefulTimeoutMs = 3000): Promise { 26 | if (this.processes.size === 0) return; 27 | 28 | console.log(`Cleaning up ${this.processes.size} processes...`); 29 | 30 | // Phase 1: Send SIGTERM to all processes 31 | for (const process of this.processes) { 32 | try { 33 | process.kill('SIGTERM'); 34 | } catch { 35 | // Process may already be dead 36 | } 37 | } 38 | 39 | // Phase 2: Wait for graceful shutdown 40 | await new Promise(resolve => setTimeout(resolve, gracefulTimeoutMs)); 41 | 42 | // Phase 3: Force kill any remaining processes 43 | for (const process of this.processes) { 44 | try { 45 | process.kill('SIGKILL'); 46 | } catch { 47 | // Process may already be dead 48 | } 49 | } 50 | 51 | this.processes.clear(); 52 | console.log('Process cleanup completed'); 53 | } 54 | } -------------------------------------------------------------------------------- /benchmarks/benchmarks-entry-server.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Main entry point for running all benchmarks using custom benchmark system. 3 | */ 4 | 5 | import { BenchmarkRunner } from './mod.ts'; 6 | import { getEnvVar } from '../base/os.ts'; 7 | import { exit } from '../base/process.ts'; 8 | import { isBrowser } from '../base/common.ts'; 9 | 10 | // Import benchmark setup functions 11 | import setupGoatDB from './goatdb.bench.ts'; 12 | import setupSQLite from './sqlite.bench.ts'; 13 | import setupSQLiteFastUnsafe from './sqlite-fast-unsafe.bench.ts'; 14 | import setupSQLiteBrowser from './sqlite-browser.bench.ts'; 15 | 16 | async function main(): Promise { 17 | // Register all benchmarks 18 | setupGoatDB(); 19 | 20 | // Register platform-specific SQLite benchmarks 21 | if (isBrowser()) { 22 | setupSQLiteBrowser(); // Browser SQLite with OPFS 23 | } else { 24 | setupSQLite(); // Node/Deno SQLite with filesystem 25 | setupSQLiteFastUnsafe(); // Node/Deno SQLite with unsafe optimizations 26 | } 27 | 28 | // Get benchmark filter from environment 29 | const benchmarkName = getEnvVar('GOATDB_BENCHMARK'); 30 | const outputJson = getEnvVar('GOATDB_OUTPUT_JSON') === 'true'; 31 | 32 | // Run our custom benchmark system with new parameters 33 | const summary = await BenchmarkRunner.default.run(benchmarkName, outputJson); 34 | 35 | if (isBrowser()) { 36 | // In browser, set global results for automation with completed flag 37 | (globalThis as any).testResults = { ...summary, completed: true }; 38 | globalThis.dispatchEvent( 39 | new CustomEvent('benchmarksComplete', { detail: summary }), 40 | ); 41 | } 42 | 43 | // Exit with code 1 if any benchmarks failed, 0 if all passed 44 | const exitCode = summary.summary.failed > 0 ? 1 : 0; 45 | await exit(exitCode); 46 | } 47 | 48 | main(); 49 | -------------------------------------------------------------------------------- /docs/docs/api/interfaces/dbinstanceconfig.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: DBInstanceConfig 3 | sidebar_label: DBInstanceConfig 4 | --- 5 | 6 | # DBInstanceConfig 7 | 8 | **Extended by:** [ServerOptions](/docs/api/interfaces/serveroptions) 9 | 10 | ## Properties 11 | 12 | ### debug 13 | 14 | ```typescript 15 | debug: boolean 16 | ``` 17 | 18 | If true, the DB will be in debug mode. 19 | Defaults to false. 20 | 21 | ### mode 22 | 23 | ```typescript 24 | mode: DBMode 25 | ``` 26 | 27 | The mode of operation for this database instance. 28 | 29 | ### orgId 30 | 31 | ```typescript 32 | orgId: string 33 | ``` 34 | 35 | Optional organization id used to sandbox the data of a specific 36 | organization in a multi-tenant deployment. Defaults to "localhost". 37 | 38 | ### path 39 | 40 | ```typescript 41 | path: string 42 | ``` 43 | 44 | Absolute path to the directory that'll store the DB's data. 45 | 46 | ### peers 47 | 48 | ```typescript 49 | peers: string | Iterable 50 | ``` 51 | 52 | Absolute URLs of peers to sync with. Peers are must share the same 53 | public/private root keys of this instance. 54 | 55 | ### registry 56 | 57 | ```typescript 58 | registry: [DataRegistry](/docs/api/classes/dataregistry)<> 59 | ``` 60 | 61 | Optional schema registry to use for this database instance. 62 | If not provided, the default global registry (DataRegistry.default) will 63 | be used. The registry contains all schema definitions and authorization 64 | rules that this database instance will work with. Authorization rules 65 | define who can read, write, or delete data based on user permissions and 66 | data properties. 67 | 68 | ### trusted 69 | 70 | ```typescript 71 | trusted: boolean 72 | ``` 73 | 74 | If true, all security mechanisms are bypassed in favor of speed. 75 | Set this to true when running purely in a trusted backend environment. 76 | Defaults to false. 77 | 78 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "workspace": ["./docs"], 3 | "compilerOptions": { 4 | "lib": ["dom", "dom.iterable", "dom.asynciterable", "deno.ns"] 5 | }, 6 | "lock": false, 7 | "fmt": { 8 | "useTabs": false, 9 | "lineWidth": 80, 10 | "indentWidth": 2, 11 | "singleQuote": true 12 | }, 13 | "name": "@goatdb/goatdb", 14 | "version": "0.4.0", 15 | "license": "AGPL/ELV2", 16 | "exports": { 17 | ".": "./mod.ts", 18 | "./react": "./react/hooks.ts", 19 | "./server": "./server/mod.ts", 20 | "./init": "./cli/init.ts", 21 | "./link": "./cli/link.ts" 22 | }, 23 | "tasks": { 24 | "build": "deno run -A system-assets/build-sys-assets.ts", 25 | "test": "deno run -A tests/run.ts", 26 | "bench": "deno run -A benchmarks/run.ts", 27 | "docs:api": "deno run -A docs/docs-api-build.ts", 28 | "docs:clean": "rm -rf docs/node_modules docs/.docusaurus docs/build docs/package-lock.json", 29 | "docs:serve": "deno run -A docs/docs-build.ts serve", 30 | "docs:build": "deno run -A docs/docs-build.ts build" 31 | }, 32 | "imports": { 33 | "@goatdb/orderstamp": "jsr:@goatdb/orderstamp@^1.1.1", 34 | "@luca/esbuild-deno-loader": "jsr:@luca/esbuild-deno-loader@^0.11.1", 35 | "@std/data-structures": "jsr:@std/data-structures@^1.0.8", 36 | "@std/expect": "jsr:@std/expect@^1.0.16", 37 | "@std/fs": "jsr:@std/fs@^1.0.17", 38 | "@std/path": "jsr:@std/path@^1.0.9", 39 | "@std/encoding": "jsr:@std/encoding@^1.0.10", 40 | "@types/nodemailer": "npm:@types/nodemailer@^6.4.17", 41 | "esbuild": "npm:esbuild@^0.25.4", 42 | "nodemailer": "npm:nodemailer@^6.10.0", 43 | "playwright": "npm:playwright@^1.48.0", 44 | "react": "npm:react@^19.1.0", 45 | "source-map": "npm:source-map@^0.7.4", 46 | "sql.js": "npm:sql.js@^1.11.0", 47 | "yargs": "npm:yargs@^17.7.2", 48 | "typedoc": "npm:typedoc@^0.28.8" 49 | }, 50 | "nodeModulesDir": "auto" 51 | } 52 | -------------------------------------------------------------------------------- /base/cache.ts: -------------------------------------------------------------------------------- 1 | interface MapOptions { 2 | ttl: number; 3 | autoRefresh: boolean; 4 | } 5 | 6 | interface MapValue { 7 | value: V; 8 | timeout: number; 9 | } 10 | 11 | const DEFAULTS = { 12 | ttl: 5 * 60 * 1000, 13 | autoRefresh: true, 14 | }; 15 | 16 | export class CacheMap { 17 | private _opts: MapOptions; 18 | private _map: Map>; 19 | 20 | constructor(opts?: MapOptions) { 21 | this._opts = { 22 | ...DEFAULTS, 23 | ...opts, 24 | }; 25 | this._map = new Map>(); 26 | } 27 | 28 | get(key: K): V | undefined { 29 | const val = this._map.get(key); 30 | if (!val) { 31 | return; 32 | } 33 | if (this._opts.autoRefresh) { 34 | clearTimeout(val.timeout); 35 | val.timeout = setTimeout(() => { 36 | this.delete(key); 37 | }, this._opts.ttl); 38 | } 39 | 40 | return val.value; 41 | } 42 | 43 | set(key: K, value: V): void { 44 | const oldVal = this._map.get(key); 45 | if (oldVal) { 46 | clearTimeout(oldVal.timeout); 47 | } 48 | const timeout = setTimeout(() => { 49 | this.delete(key); 50 | }, this._opts.ttl); 51 | this._map.set(key, { 52 | value, 53 | timeout, 54 | }); 55 | } 56 | 57 | delete(key: K) { 58 | const oldVal = this._map.get(key); 59 | if (oldVal) { 60 | clearTimeout(oldVal.timeout); 61 | } 62 | return this._map.delete(key); 63 | } 64 | 65 | has(key: K): boolean { 66 | return this._map.has(key); 67 | } 68 | } 69 | 70 | export function memoize(fn: (...args: any[]) => any, opts?: MapOptions) { 71 | const cache = new CacheMap(opts); 72 | 73 | return function (...args: any[]): any { 74 | const key = JSON.stringify(args); 75 | if (cache.has(key)) { 76 | return cache.get(key); 77 | } 78 | 79 | const result = fn(...args); 80 | cache.set(key, result); 81 | return result; 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # GoatDB Node.js Examples 2 | 3 | Quick examples showing how to use GoatDB with Node.js. 4 | 5 | ## Examples 6 | 7 | ### [nodejs-basic](./nodejs-basic/) 8 | Essential GoatDB operations in Node.js 9 | - Database setup and schemas 10 | - CRUD operations 11 | - Reactive queries 12 | 13 | ## Quick Start 14 | 15 | ```bash 16 | cd nodejs-basic 17 | npm install 18 | npm start 19 | ``` 20 | 21 | ## Core patterns 22 | 23 | ```javascript 24 | import { GoatDB, DataRegistry } from '@goatdb/goatdb'; 25 | 26 | // 1. Define schema 27 | const schema = { 28 | ns: 'task', 29 | version: 1, 30 | fields: { 31 | text: { type: 'string', required: true }, 32 | done: { type: 'boolean', default: () => false } 33 | } 34 | }; 35 | 36 | // 2. Register schema 37 | DataRegistry.default.registerSchema(schema); 38 | 39 | // 3. Initialize database 40 | const db = new GoatDB({ path: './data' }); 41 | await db.readyPromise(); 42 | 43 | // 4. Create items 44 | const task = db.create('/data/todos/task-1', schema, { text: 'Learn GoatDB' }); 45 | 46 | // 5. Query with predicates 47 | const incompleteTasks = db.query({ 48 | source: '/data/todos', 49 | schema, 50 | predicate: ({ item }) => !item.get('done') 51 | }); 52 | 53 | // 6. Reactive updates 54 | incompleteTasks.onResultsChanged(() => { 55 | console.log('Updated!', incompleteTasks.results().length); 56 | }); 57 | ``` 58 | 59 | ## Path format 60 | 61 | GoatDB uses `/type/repo/item` paths: 62 | - `/data/todos/task-1` - A task item 63 | - `/sys/users/user-123` - A user item 64 | - `/events/logs/log-456` - A log item 65 | 66 | ## Security Mode 67 | 68 | GoatDB defaults to secure mode with user authentication. For backend-only applications in secure environments, you can use `trusted: true` for better performance. [Learn more about trusted mode](https://goatdb.dev/sessions#trusted-mode). 69 | 70 | ## Requirements 71 | 72 | - Node.js 16+ 73 | - `"type": "module"` in package.json -------------------------------------------------------------------------------- /benchmarks/browser/assets/sqlite3-worker1.js: -------------------------------------------------------------------------------- 1 | /* 2 | 2022-05-23 3 | 4 | The author disclaims copyright to this source code. In place of a 5 | legal notice, here is a blessing: 6 | 7 | * May you do good and not evil. 8 | * May you find forgiveness for yourself and forgive others. 9 | * May you share freely, never taking more than you give. 10 | 11 | *********************************************************************** 12 | 13 | This is a JS Worker file for the main sqlite3 api. It loads 14 | sqlite3.js, initializes the module, and postMessage()'s a message 15 | after the module is initialized: 16 | 17 | {type: 'sqlite3-api', result: 'worker1-ready'} 18 | 19 | This seemingly superfluous level of indirection is necessary when 20 | loading sqlite3.js via a Worker. Instantiating a worker with new 21 | Worker("sqlite.js") will not (cannot) call sqlite3InitModule() to 22 | initialize the module due to a timing/order-of-operations conflict 23 | (and that symbol is not exported in a way that a Worker loading it 24 | that way can see it). Thus JS code wanting to load the sqlite3 25 | Worker-specific API needs to pass _this_ file (or equivalent) to the 26 | Worker constructor and then listen for an event in the form shown 27 | above in order to know when the module has completed initialization. 28 | 29 | This file accepts a URL arguments to adjust how it loads sqlite3.js: 30 | 31 | - `sqlite3.dir`, if set, treats the given directory name as the 32 | directory from which `sqlite3.js` will be loaded. 33 | */ 34 | 'use strict'; 35 | { 36 | const urlParams = globalThis.location 37 | ? new URL(globalThis.location.href).searchParams 38 | : new URLSearchParams(); 39 | let theJs = 'sqlite3.js'; 40 | if (urlParams.has('sqlite3.dir')) { 41 | theJs = urlParams.get('sqlite3.dir') + '/' + theJs; 42 | } 43 | 44 | importScripts(theJs); 45 | } 46 | sqlite3InitModule().then((sqlite3) => sqlite3.initWorker1API()); 47 | -------------------------------------------------------------------------------- /base/parallel-executor.ts: -------------------------------------------------------------------------------- 1 | export interface Action { 2 | (): Promise; 3 | } 4 | 5 | interface ActionEntry { 6 | action: Action; 7 | type: 'action'; 8 | } 9 | 10 | interface PromiseEntry { 11 | promise: Promise; 12 | type: 'promise'; 13 | } 14 | 15 | type Entry = ActionEntry | PromiseEntry; 16 | 17 | export class ParallelExecutor { 18 | private _queue: Entry[]; 19 | private _concurrency: number; 20 | private _awaiter?: () => void; 21 | 22 | constructor(concurrency = 20) { 23 | this._queue = []; 24 | this._concurrency = concurrency; 25 | } 26 | 27 | push(action: Action): void { 28 | const queue = this._queue; 29 | const idx = queue.length; 30 | queue.push({ action, type: 'action' }); 31 | this.activate(idx); 32 | } 33 | 34 | await(): Promise { 35 | if (this._queue.length === 0) { 36 | return Promise.resolve(); 37 | } 38 | return new Promise(res => { 39 | this._awaiter = res; 40 | }); 41 | } 42 | 43 | private activate(idx: number): void { 44 | const concurrency = this._concurrency; 45 | if (idx >= concurrency) { 46 | return; 47 | } 48 | const queue = this._queue; 49 | const entry = queue[idx]; 50 | let newEntry: PromiseEntry; 51 | if (entry.type === 'action') { 52 | const promise = entry.action().finally(() => { 53 | queue.splice(queue.indexOf(newEntry), 1); 54 | if (queue.length >= concurrency) { 55 | this.activate(concurrency - 1); 56 | } else { 57 | this.notifyIfNeeded(); 58 | } 59 | }); 60 | newEntry = { promise, type: 'promise' }; 61 | queue[idx] = newEntry; 62 | } 63 | } 64 | 65 | private notifyIfNeeded(): void { 66 | if (this._queue.length === 0) { 67 | const awaiter = this._awaiter; 68 | if (awaiter !== undefined) { 69 | this._awaiter = undefined; 70 | awaiter(); 71 | } 72 | return; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /base/json-log/file-impl-deno.ts: -------------------------------------------------------------------------------- 1 | import * as path from '@std/path'; 2 | import type { FileImpl } from './file-impl-interface.ts'; 3 | 4 | export const FileImplDeno: FileImpl = { 5 | async open(filePath, write) { 6 | // try { 7 | await Deno.mkdir(path.dirname(filePath), { recursive: true }); 8 | // } catch (_: unknown) {} 9 | return Deno.open(filePath, { 10 | read: true, 11 | write, 12 | create: write, 13 | }); 14 | }, 15 | 16 | seek(handle, offset, from) { 17 | let whence: Deno.SeekMode; 18 | switch (from) { 19 | case 'start': 20 | whence = Deno.SeekMode.Start; 21 | break; 22 | 23 | case 'current': 24 | whence = Deno.SeekMode.Current; 25 | break; 26 | 27 | case 'end': 28 | whence = Deno.SeekMode.End; 29 | break; 30 | } 31 | return handle.seek(offset, whence); 32 | }, 33 | 34 | read(handle, buf) { 35 | return handle.read(buf); 36 | }, 37 | 38 | truncate(handle, len) { 39 | return handle.truncate(len); 40 | }, 41 | 42 | async write(handle, buf) { 43 | let bytesWritten = 0; 44 | while (bytesWritten < buf.byteLength) { 45 | const arr = buf.subarray(bytesWritten); 46 | bytesWritten += await handle.write(arr); 47 | } 48 | }, 49 | 50 | close(handle) { 51 | handle.close(); 52 | return Promise.resolve(); 53 | }, 54 | 55 | flush(handle) { 56 | return handle.sync(); 57 | }, 58 | 59 | async remove(path: string): Promise { 60 | try { 61 | await Deno.remove(path, { recursive: true }); 62 | return true; 63 | } catch (_: unknown) { 64 | return false; 65 | } 66 | }, 67 | 68 | getCWD() { 69 | return Deno.cwd(); 70 | }, 71 | 72 | getTempDir() { 73 | return Deno.makeTempDir(); 74 | }, 75 | 76 | async mkdir(path: string) { 77 | try { 78 | await Deno.mkdir(path, { recursive: true }); 79 | return true; 80 | } catch (_: unknown) { 81 | return false; 82 | } 83 | }, 84 | }; 85 | -------------------------------------------------------------------------------- /server/app-config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A configuration of a Single Page Application built on top GoatDB. 3 | */ 4 | export type AppConfig = { 5 | /** 6 | * The directory in which to perform the build process. Intermediate files as 7 | * well as the final binary will be placed there. 8 | */ 9 | buildDir: string; 10 | /** 11 | * Path to the main js entry point for the client app. The server 12 | * automatically transpiles the client code for the browser code using 13 | * ESBuild. 14 | * 15 | * Supported files: .js .jsx .ts .tsx. 16 | * Accessible at `/app.js` 17 | */ 18 | jsPath: string; 19 | /** 20 | * Path to the main HTML file for the app. If provided, all unknown paths 21 | * will be redirected to this HTML file. 22 | * 23 | * Accessible at `/index.html`. 24 | */ 25 | htmlPath?: string; 26 | /** 27 | * Path to the main CSS file for the app. 28 | * 29 | * Accessible at `/index.css`. 30 | */ 31 | cssPath?: string; 32 | /** 33 | * Path to a directory containing static assets. If provided, all files under 34 | * it will be bundled into the application and be publicly accessible through 35 | * the web server. 36 | * 37 | * Accessible at `/assets/*`. 38 | */ 39 | assetsPath?: string; 40 | /** 41 | * An optional filter function for filtering only selected files out of the 42 | * assets directory. The default implementation ignores files starting with 43 | * '.'. 44 | * 45 | * @param path Path of the asset file. 46 | * @returns `true` for the path to be included in the app, `false` for it to 47 | * be skipped. 48 | */ 49 | assetsFilter?: (path: string) => boolean; 50 | /** 51 | * Path to deno.json. Defaults to 'deno.json' inside the current directory. 52 | */ 53 | denoJson?: string; 54 | /** 55 | * If set to true, the output code bundle will be minified. 56 | */ 57 | minify?: boolean; 58 | /** 59 | * Application name. Extracted from the "name" field of the project's 60 | * deno.json. 61 | */ 62 | appName?: string; 63 | }; 64 | -------------------------------------------------------------------------------- /docs/api/functions-react.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: React Functions 3 | --- 4 | 5 | # React Functions 6 | 7 | ## useDB 8 | 9 | **useDB(): [GoatDB](/api/classes/goatdb)<US>** 10 | 11 | Opens a local DB, creating it if necessary. Once opened, the DB is available 12 | as a react context. All future calls return the already opened DB rather than 13 | opening it again. 14 | 15 | This hook will trigger a re-render whenever the current user changes. 16 | 17 | ## useDBReady 18 | 19 | **useDBReady(): [DBReadyState](/api/types/dbreadystate)** 20 | 21 | A hook for monitoring the DB's loading process. The hook triggers whenever 22 | the loading process changes its state. 23 | 24 | ## useItem 25 | 26 | **useItem(pathComps: string[]): [ManagedItem](/api/classes/manageditem)<S, [Schema](/api/types/schema)>** 27 | 28 | This hook monitors changes to a specific item, triggering a re-render 29 | whenever the item's state changes. It returns a mutable ManagedItem 30 | instance that allows direct editing. Any changes to the item are 31 | automatically queued for background commit and synchronization with the 32 | server. Similar to the useQuery hook, useItem reacts to both 33 | local and remote updates. 34 | 35 | ## useQuery 36 | 37 | **useQuery(config: [UseQueryOpts](/api/interfaces/usequeryopts)<IS, CTX, OS>): [Query](/api/classes/query)<IS, OS, CTX>** 38 | 39 | Creates a new query or retrieves an existing one. On first access, GoatDB 40 | automatically loads the source repository either from the local disk or by 41 | fetching it from the server. The hook triggers UI re-rendering whenever the 42 | query results are updated, regardless of whether the changes originate from 43 | local or remote edits. 44 | 45 | When a query is first opened, it performs a linear scan of its source using a 46 | Coroutine without blocking the main thread. During and after this 47 | initial scan, the query caches its results to disk, allowing subsequent runs 48 | to resume execution from the cached state. 49 | For additional details, refer to the mechanism documentation. 50 | 51 | -------------------------------------------------------------------------------- /cfds/base/types/utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getTypeOperationsByValue, 3 | IValueTypeOperations, 4 | ValueTypeOptions, 5 | } from './index.ts'; 6 | import { Change, EncodedChange } from '../../change/index.ts'; 7 | import { CoreValue } from '../../../base/core-types/index.ts'; 8 | 9 | export function diff( 10 | value1: CoreValue, 11 | value2: CoreValue, 12 | typeOP?: IValueTypeOperations, 13 | options: ValueTypeOptions = {} 14 | ): undefined | Change | Change[] { 15 | if (value1 === undefined && value2 === undefined) return; 16 | 17 | const lTypeOP = typeOP || getTypeOperationsByValue(value1); 18 | 19 | if (value1 === undefined && value2 !== undefined) { 20 | //New Value 21 | return lTypeOP.valueAddedDiff(value2, options); 22 | } else if (value1 !== undefined && value2 === undefined) { 23 | //Value Removed 24 | return lTypeOP.valueRemovedDiff(value1, options); 25 | } else { 26 | //Value Changed 27 | return lTypeOP.valueChangedDiff(value1, value2, options); 28 | } 29 | } 30 | 31 | export function patch( 32 | value: CoreValue, 33 | changes: Change[], 34 | typeOP?: IValueTypeOperations, 35 | options: ValueTypeOptions = {} 36 | ): CoreValue { 37 | const lTypeOP = typeOP || getTypeOperationsByValue(value); 38 | const newValue = lTypeOP.patch(value, changes, options); 39 | 40 | return newValue; 41 | } 42 | 43 | export function concatChanges( 44 | changes1: undefined | Change | Change[], 45 | changes2: undefined | Change | Change[] 46 | ): Change[] { 47 | const result: Change[] = []; 48 | if (changes1) { 49 | if (Array.isArray(changes1)) { 50 | for (const c of changes1) { 51 | result.push(c); 52 | } 53 | } else { 54 | result.push(changes1); 55 | } 56 | } 57 | 58 | if (changes2) { 59 | if (Array.isArray(changes2)) { 60 | for (const c of changes2) { 61 | result.push(c); 62 | } 63 | } else { 64 | result.push(changes2); 65 | } 66 | } 67 | 68 | return result; 69 | } 70 | -------------------------------------------------------------------------------- /docs/docs/api/functions-react.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: React Functions 3 | --- 4 | 5 | # React Functions 6 | 7 | ## useDB 8 | 9 | **useDB(): [GoatDB](/docs/api/classes/goatdb)<US>** 10 | 11 | Opens a local DB, creating it if necessary. Once opened, the DB is available 12 | as a react context. All future calls return the already opened DB rather than 13 | opening it again. 14 | 15 | This hook will trigger a re-render whenever the current user changes. 16 | 17 | ## useDBReady 18 | 19 | **useDBReady(): [DBReadyState](/docs/api/types/dbreadystate)** 20 | 21 | A hook for monitoring the DB's loading process. The hook triggers whenever 22 | the loading process changes its state. 23 | 24 | ## useItem 25 | 26 | **useItem(pathComps: string[]): [ManagedItem](/docs/api/classes/manageditem)<S, [Schema](/docs/api/types/schema)>** 27 | 28 | This hook monitors changes to a specific item, triggering a re-render 29 | whenever the item's state changes. It returns a mutable ManagedItem 30 | instance that allows direct editing. Any changes to the item are 31 | automatically queued for background commit and synchronization with the 32 | server. Similar to the useQuery hook, useItem reacts to both 33 | local and remote updates. 34 | 35 | ## useQuery 36 | 37 | **useQuery(config: [UseQueryOpts](/docs/api/interfaces/usequeryopts)<IS, CTX, OS>): [Query](/docs/api/classes/query)<IS, OS, CTX>** 38 | 39 | Creates a new query or retrieves an existing one. On first access, GoatDB 40 | automatically loads the source repository either from the local disk or by 41 | fetching it from the server. The hook triggers UI re-rendering whenever the 42 | query results are updated, regardless of whether the changes originate from 43 | local or remote edits. 44 | 45 | When a query is first opened, it performs a linear scan of its source using a 46 | Coroutine without blocking the main thread. During and after this 47 | initial scan, the query caches its results to disk, allowing subsequent runs 48 | to resume execution from the cached state. 49 | For additional details, refer to the mechanism documentation. 50 | 51 | -------------------------------------------------------------------------------- /system-assets/build-sys-assets.ts: -------------------------------------------------------------------------------- 1 | import * as path from '@std/path'; 2 | import * as esbuild from 'esbuild'; 3 | import { denoPlugins } from '@luca/esbuild-deno-loader'; 4 | import { getRepositoryPath } from '../base/development.ts'; 5 | import { type StaticAssets, staticAssetsToJS } from './system-assets.ts'; 6 | 7 | export async function buildSysAssets(): Promise { 8 | const repoPath = await getRepositoryPath(); 9 | const outputDir = path.join(repoPath, 'system-assets'); 10 | const result = await esbuild.build({ 11 | entryPoints: [ 12 | { 13 | in: path.join(repoPath, 'base', 'json-log', 'json-log-worker-entry.ts'), 14 | out: 'json-log-worker', 15 | }, 16 | ], 17 | plugins: [...denoPlugins()], 18 | bundle: true, 19 | write: false, 20 | sourcemap: 'external', 21 | outdir: outputDir, 22 | // minify: true, 23 | logOverride: { 24 | 'empty-import-meta': 'silent', 25 | }, 26 | }); 27 | 28 | await Deno.mkdir(outputDir, { recursive: true }); 29 | const assets: StaticAssets = {}; 30 | for (const f of result.outputFiles) { 31 | assets['/system-assets/' + path.basename(f.path)] = { 32 | data: f.contents, 33 | contentType: f.path.endsWith('.map') 34 | ? 'application/json' 35 | : 'text/javascript', 36 | }; 37 | } 38 | assets['/system-assets/bloom_filter.js'] = { 39 | data: await Deno.readFile( 40 | path.join(repoPath, 'system-assets', 'bloom_filter.js'), 41 | ), 42 | contentType: 'text/javascript', 43 | }; 44 | assets['/system-assets/bloom-filter.wasm'] = { 45 | data: await Deno.readFile( 46 | path.join(repoPath, 'system-assets', 'bloom_filter.wasm'), 47 | ), 48 | contentType: 'application/wasm', 49 | }; 50 | assets['/system-assets/bloom_filter.wasm.map'] = { 51 | data: await Deno.readFile( 52 | path.join(repoPath, 'system-assets', 'bloom_filter.wasm.map'), 53 | ), 54 | contentType: 'application/json', 55 | }; 56 | await Deno.writeTextFile( 57 | path.join(outputDir, 'assets.json'), 58 | JSON.stringify(staticAssetsToJS(assets)), 59 | ); 60 | } 61 | 62 | buildSysAssets(); 63 | -------------------------------------------------------------------------------- /base/lru-cache.ts: -------------------------------------------------------------------------------- 1 | import { OrderedMap } from './collections/orderedmap.ts'; 2 | 3 | /** 4 | * A Least Recently Used (LRU) cache implementation that automatically evicts 5 | * the least recently used items when the cache reaches its capacity. 6 | * 7 | * Uses OrderedMap internally to maintain access order and provide O(1) operations 8 | * for lookups, insertions, and deletions. 9 | * 10 | * @template K The type of keys in the cache 11 | * @template V The type of values in the cache 12 | */ 13 | export class LRUCache { 14 | /** Ordered map that tracks both values and access order */ 15 | private _map: OrderedMap; 16 | /** Maximum number of items the cache can hold */ 17 | private _maxSize: number; 18 | 19 | /** 20 | * Creates a new LRU cache with the specified maximum size. 21 | * 22 | * @param maxSize Maximum number of items the cache will hold before evicting 23 | */ 24 | constructor(maxSize: number) { 25 | this._map = new OrderedMap(); 26 | this._maxSize = maxSize; 27 | } 28 | 29 | /** 30 | * Retrieves a value from the cache by its key and marks it as recently used. 31 | * 32 | * @param key The key to look up 33 | * @returns The value associated with the key, or undefined if not found 34 | */ 35 | get(key: K): V | undefined { 36 | this._map.moveToStart(key); 37 | return this._map.get(key); 38 | } 39 | 40 | /** 41 | * Checks if the cache contains a value for the given key. 42 | * 43 | * @param key The key to check for 44 | * @returns true if the key exists in the cache, false otherwise 45 | */ 46 | has(key: K): boolean { 47 | if (this._map.has(key)) { 48 | this._map.moveToStart(key); 49 | return true; 50 | } 51 | return false; 52 | } 53 | 54 | /** 55 | * Adds or updates a key-value pair in the cache. 56 | * If adding the new item exceeds the cache capacity, the least recently used 57 | * item will be evicted. 58 | * 59 | * @param key The key to set 60 | * @param value The value to associate with the key 61 | */ 62 | set(key: K, value: V): void { 63 | this._map.set(key, value); 64 | if (this._map.size > this._maxSize) { 65 | this._map.delete(this._map.startKey!); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /base/node-runner.ts: -------------------------------------------------------------------------------- 1 | import * as esbuild from 'npm:esbuild'; 2 | import { denoPlugins } from 'jsr:@luca/esbuild-deno-loader'; 3 | 4 | /** 5 | * Compiles a TypeScript file using esbuild for execution in Node.js and returns the build result. 6 | * 7 | * @param inputFile - Path to the TypeScript file to compile 8 | * @param outName - Output file name (without extension) 9 | * @returns The esbuild BuildResult 10 | */ 11 | export async function compileForNodeWithEsbuild( 12 | inputFile: string, 13 | outName: string, 14 | ) { 15 | return await esbuild.build({ 16 | entryPoints: [ 17 | { 18 | in: inputFile, 19 | out: outName, 20 | }, 21 | ], 22 | plugins: [...denoPlugins()], 23 | outfile: outName, 24 | bundle: true, 25 | write: false, 26 | sourcemap: 'inline', 27 | logOverride: { 28 | 'empty-import-meta': 'silent', 29 | }, 30 | }); 31 | } 32 | 33 | /** 34 | * Runs a pre-compiled esbuild result in Node.js environment. 35 | * 36 | * @param result - The esbuild BuildResult (output of compileForNodeWithEsbuild) 37 | * @param inspectBrk - Optional flag to enable Node.js inspector with break on start 38 | * @param env - Optional environment variables to set for the Node.js process 39 | * @returns A Promise that resolves to true if the Node.js process exits successfully, false otherwise 40 | */ 41 | export async function nodeRun( 42 | result: Awaited>, 43 | inspectBrk?: boolean, 44 | env?: Record, 45 | ): Promise { 46 | try { 47 | const nodeCmd = new Deno.Command('node', { 48 | stdin: 'piped', 49 | stdout: 'inherit', 50 | stderr: 'inherit', 51 | ...(inspectBrk 52 | ? { 53 | args: ['--inspect-brk'], 54 | } 55 | : {}), 56 | env: { 57 | NODE_NO_WARNINGS: '1', 58 | ...env, 59 | }, 60 | }); 61 | const nodeProcess = nodeCmd.spawn(); 62 | const writer = nodeProcess.stdin.getWriter(); 63 | await writer.write(result.outputFiles![0].contents); 64 | await writer.close(); 65 | await nodeProcess.output(); 66 | return (await nodeProcess.status).success; 67 | } catch (_: unknown) { 68 | return false; 69 | } finally { 70 | await esbuild.stop(); 71 | } 72 | } -------------------------------------------------------------------------------- /base/collections/lru-cache.ts: -------------------------------------------------------------------------------- 1 | import { Dictionary } from './dict.ts'; 2 | import { OrderedMap } from './orderedmap.ts'; 3 | 4 | export type EvictionHandler = (key: K, value: V) => void; 5 | 6 | export class LRUCache implements Dictionary { 7 | private readonly _map: OrderedMap; 8 | private readonly _evictionHandler?: EvictionHandler; 9 | private _limit: number; 10 | 11 | constructor(limit?: number, evictionHandler?: EvictionHandler) { 12 | this._map = new OrderedMap(); 13 | this._evictionHandler = evictionHandler; 14 | this._limit = Math.max(0, limit || 0); 15 | } 16 | 17 | get size(): number { 18 | return this._map.size; 19 | } 20 | 21 | get limit(): number { 22 | return this._limit; 23 | } 24 | 25 | set limit(limit: number) { 26 | this._limit = Math.max(0, limit); 27 | this.evictValuesIfNeeded(); 28 | } 29 | 30 | get(key: K): V | undefined { 31 | this._map.moveToEnd(key); 32 | return this._map.get(key); 33 | } 34 | 35 | has(key: K): boolean { 36 | this._map.moveToEnd(key); 37 | return this._map.has(key); 38 | } 39 | 40 | entries(): Iterable<[K, V]> { 41 | return this._map.entries(); 42 | } 43 | 44 | keys(): Iterable { 45 | return this._map.keys(); 46 | } 47 | 48 | values(): Iterable { 49 | return this._map.values(); 50 | } 51 | 52 | [Symbol.iterator](): Iterator<[K, V]> { 53 | return this._map[Symbol.iterator](); 54 | } 55 | 56 | set(key: K, value: V): void { 57 | const map = this._map; 58 | const valueExists = map.has(key); 59 | map.set(key, value); 60 | if (valueExists) { 61 | map.moveToEnd(key); 62 | } else { 63 | this.evictValuesIfNeeded(); 64 | } 65 | } 66 | 67 | delete(key: K): boolean { 68 | return this._map.delete(key); 69 | } 70 | 71 | clear(): void { 72 | this._map.clear(); 73 | } 74 | 75 | private evictValuesIfNeeded(): void { 76 | const map = this._map; 77 | const limit = this._limit; 78 | const evictionHandler = this._evictionHandler; 79 | while (limit > 0 && map.size > limit) { 80 | const key = map.startKey!; 81 | if (evictionHandler) { 82 | evictionHandler(key, map.get(key)!); 83 | } 84 | map.delete(map.startKey!); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /docs/docs-build.ts: -------------------------------------------------------------------------------- 1 | import { cli } from '../base/development.ts'; 2 | import { zip } from 'jsr:@deno-library/compress'; 3 | 4 | export async function buildDocs(): Promise { 5 | await cli('rm', '-rf', 'build/docs'); 6 | await cli('mkdir', '-p', 'build/docs'); 7 | 8 | // Build Docusaurus using Deno's npm: specifier 9 | const buildProcess = new Deno.Command('deno', { 10 | args: [ 11 | 'run', 12 | '-A', 13 | 'npm:@docusaurus/core', 14 | 'build', 15 | '--out-dir', 16 | '../build/docs', 17 | ], 18 | cwd: 'docs', // Run from docs directory where docusaurus.config.ts is located 19 | stdout: 'inherit', 20 | stderr: 'inherit', 21 | }); 22 | 23 | const { code } = await buildProcess.output(); 24 | if (code !== 0) { 25 | throw new Error(`Docusaurus build failed with code ${code}`); 26 | } 27 | 28 | // Compress the contents of build/docs, not the folder itself 29 | await zip.compress('build/docs', 'build/docs.zip', { excludeSrc: true }); 30 | console.log('Docs built successfully under build/docs'); 31 | } 32 | 33 | async function serveDocs(): Promise { 34 | console.log('🚀 Starting Docusaurus development server...'); 35 | 36 | // Start the dev server process 37 | const serveProcess = new Deno.Command('deno', { 38 | args: [ 39 | 'run', 40 | '-A', 41 | 'npm:@docusaurus/core', 42 | 'start', 43 | ], 44 | cwd: 'docs', // Run from docs directory where docusaurus.config.ts is located 45 | stdout: 'inherit', 46 | stderr: 'inherit', 47 | }); 48 | 49 | // Spawn and don't wait - dev servers should run indefinitely 50 | const child = serveProcess.spawn(); 51 | 52 | console.log('📡 Server starting at http://localhost:3000'); 53 | console.log('Press Ctrl+C to stop the server'); 54 | 55 | // Wait for the process to exit (only happens when user stops it) 56 | const status = await child.status; 57 | 58 | if (status.code !== 0) { 59 | throw new Error(`Docusaurus serve failed with code ${status.code}`); 60 | } 61 | } 62 | 63 | if (import.meta.main) { 64 | const cmd = Deno.args[0]; 65 | if (!cmd || (cmd !== 'build' && cmd !== 'serve')) { 66 | console.error('Usage: deno run -A docs-build.ts '); 67 | Deno.exit(1); 68 | } 69 | if (cmd === 'build') { 70 | await buildDocs(); 71 | } else if (cmd === 'serve') { 72 | await serveDocs(); 73 | } 74 | } -------------------------------------------------------------------------------- /logging/log.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | LogEntryDeveloperError, 3 | OperationalErrorLogEntry, 4 | SystemErrorLogEntry, 5 | } from './errors.ts'; 6 | import { 7 | type GenericLogEntry, 8 | type NormalizedLogEntry, 9 | normalizeLogEntry, 10 | type Severity, 11 | SeverityCodes, 12 | } from './entry.ts'; 13 | import type { MetricLogEntry } from './metrics.ts'; 14 | import type { ClientEventEntry } from './client-events.ts'; 15 | import { ConsoleLogStream } from './console-stream.ts'; 16 | 17 | /** 18 | * A union type of all possible log entries. 19 | */ 20 | export type LogEntry = 21 | | GenericLogEntry 22 | | LogEntryDeveloperError 23 | | OperationalErrorLogEntry 24 | | SystemErrorLogEntry 25 | | MetricLogEntry 26 | | ClientEventEntry; 27 | 28 | export interface LogStream { 29 | appendEntry(e: NormalizedLogEntry): void | Promise; 30 | } 31 | 32 | // TODO: Capture anonymous logs on client and sync them with the server 33 | const kDefaultLoggerStreams: LogStream[] = [new ConsoleLogStream()]; 34 | 35 | let gLogStreams: readonly LogStream[] = kDefaultLoggerStreams; 36 | 37 | let gLogLevel = SeverityCodes.DEFAULT; 38 | 39 | export function setGlobalLoggerStreams(streams: readonly LogStream[]): void { 40 | gLogStreams = streams; 41 | } 42 | 43 | export function getGlobalLoggerStreams(): readonly LogStream[] { 44 | return gLogStreams; 45 | } 46 | 47 | export function resetGlobalLoggerStreams(): void { 48 | gLogStreams = kDefaultLoggerStreams; 49 | } 50 | 51 | export function setGlobalLoggerSeverity(level: Severity): void { 52 | gLogLevel = SeverityCodes[level]; 53 | } 54 | 55 | export function log( 56 | entry: LogEntry, 57 | outputStreams: readonly LogStream[] | undefined = gLogStreams, 58 | ): void { 59 | if (!outputStreams) { 60 | outputStreams = gLogStreams; 61 | } 62 | if (SeverityCodes[entry.severity] >= gLogLevel) { 63 | const e = normalizeLogEntry(entry); 64 | for (const stream of outputStreams) { 65 | stream.appendEntry(e); 66 | } 67 | } 68 | } 69 | 70 | export interface Logger { 71 | log(entry: LogEntry): void; 72 | } 73 | 74 | export function newLogger(outputStreams: LogStream[]): Logger { 75 | return { 76 | log(entry: LogEntry): void { 77 | log(entry, outputStreams); 78 | }, 79 | }; 80 | } 81 | 82 | export const GlobalLogger = { 83 | log(entry: LogEntry): void { 84 | log(entry, gLogStreams); 85 | }, 86 | }; 87 | -------------------------------------------------------------------------------- /base/os.ts: -------------------------------------------------------------------------------- 1 | import { isBrowser, isDeno, isNode } from './common.ts'; 2 | import { notReached } from './error.ts'; 3 | 4 | /** 5 | * Represents the possible operating systems that can be detected. 6 | * Used by the `getOS()` function to identify the current platform. 7 | */ 8 | export type OperatingSystem = 9 | | 'darwin' 10 | | 'linux' 11 | | 'android' 12 | | 'windows' 13 | | 'freebsd' 14 | | 'netbsd' 15 | | 'aix' 16 | | 'solaris' 17 | | 'illumos'; 18 | 19 | /** 20 | * Returns the current operating system. 21 | * 22 | * Detects the OS in both Deno and Node.js environments. 23 | * 24 | * @returns The detected operating system 25 | * @throws If called in an unsupported runtime 26 | */ 27 | export function getOS(): OperatingSystem { 28 | if (isDeno()) { 29 | return Deno.build.os; 30 | } else if (isNode()) { 31 | const os = require('node:os'); 32 | return os.platform(); 33 | } 34 | notReached('Platform not supported'); 35 | } 36 | 37 | /** 38 | * Checks if the current operating system is macOS. 39 | * 40 | * @returns true if macOS, false otherwise 41 | */ 42 | export function isMac(): boolean { 43 | return getOS() === 'darwin'; 44 | } 45 | 46 | /** 47 | * Checks if the current operating system is Linux. 48 | * 49 | * @returns true if Linux, false otherwise 50 | */ 51 | export function isLinux(): boolean { 52 | return getOS() === 'linux'; 53 | } 54 | 55 | /** 56 | * Checks if the current operating system is Windows. 57 | * 58 | * @returns true if Windows, false otherwise 59 | */ 60 | export function isWindows(): boolean { 61 | return getOS() === 'windows'; 62 | } 63 | 64 | /** 65 | * Retrieves the value of an environment variable in a cross-platform way. 66 | * 67 | * @param key The environment variable name (e.g., "GOATDB_SUITE") 68 | * @returns The value of the environment variable, or undefined if not found. 69 | */ 70 | export function getEnvVar(key: string): string | undefined { 71 | if (isDeno()) { 72 | // Deno: Use Deno.env.get if available 73 | return Deno.env.get?.(key); 74 | } else if (isNode()) { 75 | // Node.js: Use process.env 76 | return globalThis.process.env?.[key]; 77 | } else if (isBrowser()) { 78 | // Browser: Look for GoatDBConfig global object 79 | const config = (globalThis as any).GoatDBConfig; 80 | // Remove "GOATDB_" prefix and lowercase the key for browser config 81 | return config?.[key.toLowerCase().replace('goatdb_', '')]; 82 | } 83 | notReached('Platform not supported'); 84 | } -------------------------------------------------------------------------------- /cfds/richtext/tree-keys.ts: -------------------------------------------------------------------------------- 1 | import { Dictionary } from '../../base/collections/dict.ts'; 2 | import { HashMap } from '../../base/collections/hash-map.ts'; 3 | import { 4 | Clonable, 5 | Equatable, 6 | CoreValue, 7 | coreValueEquals, 8 | } from '../../base/core-types/index.ts'; 9 | import { encodableValueHash } from '../../base/core-types/encoding/index.ts'; 10 | import { dfs, ElementNode, kCoreValueTreeNodeOpts } from './tree.ts'; 11 | import { isElementNode } from './tree.ts'; 12 | import { isTextNode } from './tree.ts'; 13 | 14 | export type NodeKey = string; 15 | 16 | export class TreeKeys implements Clonable, Equatable { 17 | private readonly _root: ElementNode; 18 | private readonly _nodeToKey: Dictionary; 19 | private readonly _keyToNode: Dictionary; 20 | private readonly _hashToCount: Dictionary; 21 | 22 | constructor(root: ElementNode) { 23 | this._root = root; 24 | this._nodeToKey = new HashMap( 25 | encodableValueHash, 26 | (n1, n2) => n1 === n2, 27 | ); 28 | this._keyToNode = new Map(); 29 | this._hashToCount = new Map(); 30 | for (const [node] of dfs(root)) { 31 | this.keyFor(node); 32 | } 33 | } 34 | 35 | keyFor(node: CoreValue): NodeKey { 36 | let result = this._nodeToKey.get(node); 37 | if (!result) { 38 | const hash = encodableValueHash(node, kCoreValueTreeNodeOpts); 39 | const idx: number = this._hashToCount.get(hash) || 0; 40 | const tagName = 41 | (isElementNode(node) && node.tagName) || 42 | (isTextNode(node) && 'text') || 43 | undefined; 44 | result = `${tagName || ''}/${hash}/${idx}`; 45 | this._hashToCount.set(hash, idx + 1); 46 | this._nodeToKey.set(node, result); 47 | this._keyToNode.set(result, node); 48 | } 49 | return result; 50 | } 51 | 52 | clone(): TreeKeys { 53 | return new TreeKeys(this._root); 54 | } 55 | 56 | isEqual(other: TreeKeys): boolean { 57 | const thisCounts = this._nodeToKey; 58 | const otherCounts = other._nodeToKey; 59 | for (const [key, value] of thisCounts) { 60 | if (otherCounts.get(key) !== value) { 61 | return false; 62 | } 63 | } 64 | for (const key of otherCounts.keys()) { 65 | if (!thisCounts.has(key)) { 66 | return false; 67 | } 68 | } 69 | return true; 70 | } 71 | 72 | nodeFromKey(key: string): CoreValue | undefined { 73 | return this._keyToNode.get(key); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /logging/json-log-stream.ts: -------------------------------------------------------------------------------- 1 | import type { LogEntry, LogStream } from './log.ts'; 2 | import { 3 | type JSONLogFile, 4 | JSONLogFileAppend, 5 | JSONLogFileClose, 6 | JSONLogFileFlush, 7 | JSONLogFileOpen, 8 | } from '../base/json-log/json-log.ts'; 9 | 10 | import type { NormalizedLogEntry } from './entry.ts'; 11 | import { randomInt } from '../base/math.ts'; 12 | import { SimpleTimer } from '../base/timer.ts'; 13 | import { kMinuteMs } from '../base/date.ts'; 14 | 15 | /** 16 | * A LogStream implementation that writes log entries to a JSON file. 17 | * Supports optional throttling to reduce the number of entries written. 18 | */ 19 | export class JSONLogStream implements LogStream { 20 | /** Promise resolving to the underlying JSON log file */ 21 | private _log?: JSONLogFile; 22 | private _closeTimer: SimpleTimer; 23 | 24 | /** 25 | * Creates a new JSONLogStream 26 | * @param path Path to the JSON log file 27 | * @param throttleRate Optional throttling rate - only 1/throttleRate entries 28 | * will be written. Default is 1 (no throttling). 29 | */ 30 | constructor(readonly path: string, readonly throttleRate: number = 1) { 31 | this._closeTimer = new SimpleTimer( 32 | kMinuteMs, 33 | false, 34 | () => this.close(), 35 | `JSONLogStream-${path}`, 36 | ); 37 | } 38 | 39 | /** 40 | * Appends a log entry to the JSON file, respecting the throttle rate 41 | * @param e The normalized log entry to append 42 | */ 43 | async appendEntry(e: NormalizedLogEntry): Promise { 44 | if (!this._log) { 45 | this._log = await JSONLogFileOpen(this.path, true); 46 | } 47 | if (this.throttleRate <= 1 || randomInt(0, this.throttleRate) === 0) { 48 | await JSONLogFileAppend(this._log, [e]); 49 | } 50 | this._closeTimer.reset(); 51 | } 52 | 53 | /** 54 | * Closes the JSON log file if it's open. This is handled automatically by the 55 | * stream and there's no need to call it explicitly under normal operation. 56 | * 57 | * You may want to call this explicitly when: 58 | * - You need to ensure logs are flushed to disk immediately 59 | * - You're shutting down the application and want to clean up resources 60 | * - You want to release the file handle before the automatic timeout 61 | */ 62 | async close(): Promise { 63 | if (this._log) { 64 | await JSONLogFileFlush(this._log); 65 | await JSONLogFileClose(this._log); 66 | this._log = undefined; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /base/core-types/utils.ts: -------------------------------------------------------------------------------- 1 | import { isGenerator } from '../comparisons.ts'; 2 | import { notReached } from '../error.ts'; 3 | import { isDictionary } from '../collections/dict.ts'; 4 | import { 5 | Clonable, 6 | Comparable, 7 | CoreClassObject, 8 | CoreType, 9 | CoreValue, 10 | Encodable, 11 | Equatable, 12 | ReadonlyCoreObject, 13 | } from './base.ts'; 14 | 15 | export function getCoreTypeOrUndef(value: any): CoreType | undefined { 16 | if (value === undefined) { 17 | return CoreType.Undefined; 18 | } 19 | 20 | if (value === null) { 21 | return CoreType.Null; 22 | } 23 | 24 | if (typeof value === 'string') { 25 | return CoreType.String; 26 | } 27 | 28 | if (typeof value === 'number') { 29 | return CoreType.Number; 30 | } 31 | 32 | if (typeof value === 'boolean') { 33 | return CoreType.Boolean; 34 | } 35 | 36 | if (value instanceof Date) { 37 | return CoreType.Date; 38 | } 39 | 40 | if (value instanceof Array) { 41 | return CoreType.Array; 42 | } 43 | 44 | if (value instanceof Set) { 45 | return CoreType.Set; 46 | } 47 | 48 | if (isDictionary(value)) { 49 | return CoreType.Dictionary; 50 | } 51 | 52 | if (isGenerator(value)) { 53 | return CoreType.Generator; 54 | } 55 | 56 | if (typeof value === 'object') { 57 | return Object.getPrototypeOf(value).constructor === Object 58 | ? CoreType.Object 59 | : CoreType.ClassObject; 60 | } 61 | } 62 | 63 | export function getCoreType(value: CoreValue): CoreType { 64 | const type = getCoreTypeOrUndef(value); 65 | return type !== undefined ? type : notReached('Unsupported value type'); 66 | } 67 | 68 | export function isCoreClassObject(v: any): v is CoreClassObject { 69 | return isClonable(v) || isComparable(v) || isEquatable(v) || isEncodable(v); 70 | } 71 | 72 | export function isClonable(v: any): v is Clonable { 73 | return v !== undefined && typeof v.clone === 'function'; 74 | } 75 | 76 | export function isComparable(v: any): v is Comparable { 77 | return v !== undefined && typeof v.compare === 'function'; 78 | } 79 | 80 | export function isEquatable(v: any): v is Equatable { 81 | return v !== undefined && typeof v.isEqual === 'function'; 82 | } 83 | 84 | export function isEncodable>(v: any): v is T { 85 | if (v === undefined) { 86 | return false; 87 | } 88 | return v.serialize !== undefined; 89 | } 90 | 91 | export function isReadonlyCoreObject(v: any): v is ReadonlyCoreObject { 92 | return getCoreTypeOrUndef(v) === CoreType.Object; 93 | } 94 | -------------------------------------------------------------------------------- /docs/api/classes/dataregistry.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: DataRegistry 3 | sidebar_label: DataRegistry 4 | --- 5 | 6 | # DataRegistry 7 | 8 | The DataRegistry acts as a registry of known schemas for a given GoatDB 9 | instance. It's initialized when the app starts and stays fixed during its 10 | execution. 11 | 12 | Typically, apps use the `DataRegistry.default` instance, but are free to 13 | create multiple registries each with different schemas registered. 14 | 15 | ## Constructor 16 | 17 | **new DataRegistry()** 18 | 19 | Initialize a new DataRegistry. 20 | 21 | ## Methods 22 | 23 | ### authRuleForRepo() 24 | 25 | **authRuleForRepo(inputPath: string): [AuthRule](/api/types/authrule)** 26 | 27 | Finds the authorization rule for the provided path. 28 | 29 | ### decode() 30 | 31 | **decode(str: string): [Schema](/api/types/schema)** 32 | 33 | Decodes a schema marker to an actual schema. 34 | 35 | ### encode() 36 | 37 | **encode(schema: [Schema](/api/types/schema)): string** 38 | 39 | Encodes a schema to a marker string for storage. 40 | 41 | ### get() 42 | 43 | **get(ns: string, version: number): [Schema](/api/types/schema)** 44 | 45 | Find a schema that's been registered with this registry. 46 | 47 | ### registerAuthRule() 48 | 49 | **registerAuthRule(path: string | RegExp<>, rule: [AuthRule](/api/types/authrule)): void** 50 | 51 | Registers an authorization rule with this registry. If not provided, all 52 | data is considered public. 53 | 54 | ### registerSchema() 55 | 56 | **registerSchema(schema: [Schema](/api/types/schema)): void** 57 | 58 | Registers a schema with this registry. This is a NOP if the schema had 59 | already been registered. 60 | 61 | ### upgrade() 62 | 63 | **upgrade(data: [CoreObject](/api/interfaces/coreobject)<>, dataSchema: [Schema](/api/types/schema), targetSchema: [Schema](/api/types/schema)): [[CoreObject](/api/interfaces/coreobject)<[CoreValue](/api/types/corevalue)>, [Schema](/api/types/schema)]** 64 | 65 | Given a data object and its schema, this method performs the upgrade 66 | procedure to the target schema. 67 | 68 | This method will refuse to upgrade to the target schema if a single version 69 | is missing. For example, if attempting to upgrade from v1 to v3, but the 70 | v2 schema is missing, then the upgrade will be refused. 71 | 72 | NOTE: You shouldn't use this method directly under normal circumstances. 73 | The upgrade procedure will be performed automatically for you when needed. 74 | 75 | --------------------------------------------------------------------------------