rip-lang 3.14.0 → 3.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -32
- package/docs/RIP-LANG.md +89 -4
- package/docs/RIP-SCHEMA.md +98 -8
- package/docs/dist/rip.js +724 -455
- package/docs/dist/rip.min.js +232 -177
- package/docs/dist/rip.min.js.br +0 -0
- package/docs/extensions/duckdb/index.html +97 -0
- package/docs/extensions/duckdb/manifest.json +11 -0
- package/docs/extensions/duckdb/v1.5.2/linux_amd64/ripdb.duckdb_extension.gz +0 -0
- package/docs/extensions/duckdb/v1.5.2/osx_arm64/ripdb.duckdb_extension.gz +0 -0
- package/docs/extensions/index.html +82 -0
- package/docs/extensions/vscode/print/index.html +40 -0
- package/docs/extensions/vscode/print/print-1.0.13.vsix +0 -0
- package/docs/extensions/vscode/print/print-latest.vsix +0 -0
- package/docs/extensions/vscode/rip/index.html +40 -0
- package/docs/extensions/vscode/rip/rip-0.5.15.vsix +0 -0
- package/docs/extensions/vscode/rip/rip-latest.vsix +0 -0
- package/package.json +3 -2
- package/src/AGENTS.md +32 -0
- package/src/compiler.js +140 -22
- package/src/grammar/grammar.rip +41 -0
- package/src/lexer.js +146 -17
- package/src/parser.js +213 -205
- package/src/schema.js +104 -13
- package/src/typecheck.js +532 -3
- package/src/sourcemap-utils.js +0 -521
package/src/schema.js
CHANGED
|
@@ -1432,6 +1432,11 @@ function serializeLiteral(v) {
|
|
|
1432
1432
|
// its own arg shape — we centralize the parsing here so Layer 2 can rely
|
|
1433
1433
|
// on normalized structures.
|
|
1434
1434
|
function compileDirectiveArgsLiteral(name, tokens) {
|
|
1435
|
+
// @idStart requires its arg, so validate before the generic empty-bail.
|
|
1436
|
+
if (name === 'idStart' && !tokens.length) {
|
|
1437
|
+
throw schemaError(null,
|
|
1438
|
+
'@idStart requires an integer literal, e.g. @idStart 10001.');
|
|
1439
|
+
}
|
|
1435
1440
|
if (!tokens.length) return null;
|
|
1436
1441
|
|
|
1437
1442
|
// Relation directives: `@belongs_to Org`, `@belongs_to Org?`,
|
|
@@ -1489,6 +1494,29 @@ function compileDirectiveArgsLiteral(name, tokens) {
|
|
|
1489
1494
|
return `[{${parts.join(', ')}}]`;
|
|
1490
1495
|
}
|
|
1491
1496
|
|
|
1497
|
+
// @idStart N sets the seed value for the table's auto-id sequence.
|
|
1498
|
+
// Accepts a single integer literal (optionally negative). Consumed by
|
|
1499
|
+
// .toSQL(); models that never call .toSQL() simply ignore it.
|
|
1500
|
+
if (name === 'idStart') {
|
|
1501
|
+
let tok = tokens[0];
|
|
1502
|
+
let sign = 1;
|
|
1503
|
+
let numTok = tok;
|
|
1504
|
+
if (tok && tok[0] === '-' && tokens[1] && tokens[1][0] === 'NUMBER') {
|
|
1505
|
+
sign = -1;
|
|
1506
|
+
numTok = tokens[1];
|
|
1507
|
+
}
|
|
1508
|
+
if (!numTok || numTok[0] !== 'NUMBER') {
|
|
1509
|
+
throw schemaError(tok || tokens[tokens.length - 1],
|
|
1510
|
+
'@idStart requires an integer literal, e.g. @idStart 10001.');
|
|
1511
|
+
}
|
|
1512
|
+
let n = sign * Number(numTok[1]);
|
|
1513
|
+
if (!Number.isInteger(n)) {
|
|
1514
|
+
throw schemaError(numTok,
|
|
1515
|
+
'@idStart requires an integer literal; got ' + numTok[1] + '.');
|
|
1516
|
+
}
|
|
1517
|
+
return '[{value: ' + n + '}]';
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1492
1520
|
// Bare flag-like directives (@timestamps, @softDelete) don't take args.
|
|
1493
1521
|
// Anything else — capture as raw literal tokens conservatively.
|
|
1494
1522
|
return null;
|
|
@@ -1731,6 +1759,14 @@ function schemaError(tok, message) {
|
|
|
1731
1759
|
// :mixin — non-instantiable; raises `Cannot parse :mixin`
|
|
1732
1760
|
// :model — Phase 4 (the class additionally wires ORM methods)
|
|
1733
1761
|
|
|
1762
|
+
// Schema runtime ABI version. Bump when the shape of a __schema({...})
|
|
1763
|
+
// descriptor or any cross-bundle-visible runtime surface changes
|
|
1764
|
+
// incompatibly. Two bundles that disagree on this number can't share
|
|
1765
|
+
// one runtime, so a mismatch at load time throws rather than silently
|
|
1766
|
+
// fragmenting. Tracks runtime contract — not the rip-lang product
|
|
1767
|
+
// semver.
|
|
1768
|
+
const SCHEMA_RUNTIME_ABI_VERSION = 1;
|
|
1769
|
+
|
|
1734
1770
|
const SCHEMA_RUNTIME = `
|
|
1735
1771
|
// ---- Rip Schema Runtime ----------------------------------------------------
|
|
1736
1772
|
// Four layers, lazy compilation:
|
|
@@ -1740,6 +1776,26 @@ const SCHEMA_RUNTIME = `
|
|
|
1740
1776
|
// 3 (validator) compiled validator plan. Built on first .parse.
|
|
1741
1777
|
// 4a (ORM plan) built on first .find/.create/.save.
|
|
1742
1778
|
// 4b (DDL plan) built on first .toSQL(). Independent of 4a.
|
|
1779
|
+
//
|
|
1780
|
+
// Instance-singleton model:
|
|
1781
|
+
// The runtime installs itself on globalThis.__ripSchema the first time a
|
|
1782
|
+
// compiled bundle executes. Subsequent bundles that inject the same runtime
|
|
1783
|
+
// template detect the existing installation and bind to it instead of
|
|
1784
|
+
// re-running the body — giving every bundle a single shared registry,
|
|
1785
|
+
// adapter, and class identity. The IIFE wrapper below enforces that.
|
|
1786
|
+
|
|
1787
|
+
var { __schema, SchemaError, __SchemaRegistry, __schemaSetAdapter } = (function() {
|
|
1788
|
+
if (typeof globalThis !== 'undefined' && globalThis.__ripSchema) {
|
|
1789
|
+
if (globalThis.__ripSchema.__version !== ${SCHEMA_RUNTIME_ABI_VERSION}) {
|
|
1790
|
+
throw new Error(
|
|
1791
|
+
"rip-schema runtime version mismatch: loaded runtime is v" +
|
|
1792
|
+
globalThis.__ripSchema.__version +
|
|
1793
|
+
", but this bundle expects v" + ${SCHEMA_RUNTIME_ABI_VERSION} +
|
|
1794
|
+
". Two compiled Rip bundles with incompatible schema runtimes are loaded in the same process."
|
|
1795
|
+
);
|
|
1796
|
+
}
|
|
1797
|
+
return globalThis.__ripSchema;
|
|
1798
|
+
}
|
|
1743
1799
|
|
|
1744
1800
|
class SchemaError extends Error {
|
|
1745
1801
|
constructor(issues, schemaName, schemaKind) {
|
|
@@ -2051,13 +2107,12 @@ class __SchemaDef {
|
|
|
2051
2107
|
hooks.set(e.name, e.fn);
|
|
2052
2108
|
break;
|
|
2053
2109
|
case 'directive': {
|
|
2054
|
-
if (e.name === 'mixin') {
|
|
2055
|
-
// Deferred to the post-pass so we can dedupe diamond includes
|
|
2056
|
-
// and detect cycles with a full expansion stack.
|
|
2057
|
-
directives.push({ name: e.name, args: e.args || [] });
|
|
2058
|
-
break;
|
|
2059
|
-
}
|
|
2060
2110
|
directives.push({ name: e.name, args: e.args || [] });
|
|
2111
|
+
// @mixin is recorded but further handling is deferred to the
|
|
2112
|
+
// post-pass so we can dedupe diamond includes and detect
|
|
2113
|
+
// cycles with a full expansion stack. All other directives
|
|
2114
|
+
// get their relation / timestamps / softDelete processing now.
|
|
2115
|
+
if (e.name === 'mixin') break;
|
|
2061
2116
|
if (e.name === 'timestamps') timestamps = true;
|
|
2062
2117
|
if (e.name === 'softDelete') softDelete = true;
|
|
2063
2118
|
const rel = __schemaNormalizeDirectiveRelation(e, this.name);
|
|
@@ -2232,11 +2287,20 @@ class __SchemaDef {
|
|
|
2232
2287
|
enumerable: false, configurable: true, writable: true,
|
|
2233
2288
|
value: function() { return def._validateFields(this, true); },
|
|
2234
2289
|
});
|
|
2290
|
+
// toJSON mirrors the instance's own enumerable properties, which by
|
|
2291
|
+
// construction are: the primary key, declared fields, @timestamps
|
|
2292
|
+
// columns, @softDelete timestamp, @belongs_to FK columns, and any
|
|
2293
|
+
// !> eager-derived fields. Internal state (_dirty, _persisted,
|
|
2294
|
+
// _snapshot) is defined non-enumerable; methods and ~> computed
|
|
2295
|
+
// getters live on the prototype. So iterating own keys picks up
|
|
2296
|
+
// exactly the user-facing wire shape without special-casing each
|
|
2297
|
+
// category — and stays correct when new implicit columns get added
|
|
2298
|
+
// to the runtime.
|
|
2235
2299
|
Object.defineProperty(klass.prototype, 'toJSON', {
|
|
2236
2300
|
enumerable: false, configurable: true, writable: true,
|
|
2237
2301
|
value: function() {
|
|
2238
2302
|
const out = {};
|
|
2239
|
-
for (const k of
|
|
2303
|
+
for (const k of Object.keys(this)) out[k] = this[k];
|
|
2240
2304
|
return out;
|
|
2241
2305
|
},
|
|
2242
2306
|
});
|
|
@@ -2818,6 +2882,13 @@ async function __schemaSave(def, inst) {
|
|
|
2818
2882
|
}
|
|
2819
2883
|
}
|
|
2820
2884
|
}
|
|
2885
|
+
// Now that the RETURNING columns (id, @timestamps, FKs) are on the
|
|
2886
|
+
// instance, !> eager-derived fields can see them. Mirrors the hydrate
|
|
2887
|
+
// path, which runs _applyEagerDerived once all declared fields are
|
|
2888
|
+
// populated. Per-docs semantics ("materialize once, not reactive")
|
|
2889
|
+
// still hold — we're firing once, at end of construction, not on
|
|
2890
|
+
// subsequent mutations.
|
|
2891
|
+
def._applyEagerDerived(inst);
|
|
2821
2892
|
inst._persisted = true;
|
|
2822
2893
|
} else {
|
|
2823
2894
|
const sets = [], values = [];
|
|
@@ -2874,7 +2945,8 @@ const __SCHEMA_SQL_TYPES = {
|
|
|
2874
2945
|
};
|
|
2875
2946
|
|
|
2876
2947
|
function __schemaToSQL(def, options) {
|
|
2877
|
-
const
|
|
2948
|
+
const opts = options || {};
|
|
2949
|
+
const { dropFirst = false, header } = opts;
|
|
2878
2950
|
const norm = def._normalize();
|
|
2879
2951
|
const blocks = [];
|
|
2880
2952
|
if (header) blocks.push(header);
|
|
@@ -2885,6 +2957,23 @@ function __schemaToSQL(def, options) {
|
|
|
2885
2957
|
blocks.push('DROP TABLE IF EXISTS ' + table + ' CASCADE;\\nDROP SEQUENCE IF EXISTS ' + seq + ';');
|
|
2886
2958
|
}
|
|
2887
2959
|
|
|
2960
|
+
// Sequence seed: explicit option wins over @idStart directive wins over 1.
|
|
2961
|
+
// DuckDB 1.5.2 does not implement ALTER SEQUENCE ... RESTART WITH N, so the
|
|
2962
|
+
// baseline has to be set at creation — hence the knob lives here, not in a
|
|
2963
|
+
// post-create migration.
|
|
2964
|
+
let idStart = 1;
|
|
2965
|
+
for (const d of norm.directives) {
|
|
2966
|
+
if (d.name === 'idStart' && d.args?.[0] && Number.isInteger(d.args[0].value)) {
|
|
2967
|
+
idStart = d.args[0].value;
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
if (opts.idStart !== undefined) {
|
|
2971
|
+
if (!Number.isInteger(opts.idStart)) {
|
|
2972
|
+
throw new Error('schema.toSQL(): idStart must be an integer; got ' + String(opts.idStart));
|
|
2973
|
+
}
|
|
2974
|
+
idStart = opts.idStart;
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2888
2977
|
const columns = [];
|
|
2889
2978
|
const indexes = [];
|
|
2890
2979
|
columns.push(' ' + norm.primaryKey + " INTEGER PRIMARY KEY DEFAULT nextval('" + seq + "')");
|
|
@@ -2921,7 +3010,7 @@ function __schemaToSQL(def, options) {
|
|
|
2921
3010
|
indexes.push('CREATE ' + u + 'INDEX idx_' + table + '_' + fields.join('_') + ' ON ' + table + ' (' + fields.map(f => '"' + f + '"').join(', ') + ');');
|
|
2922
3011
|
}
|
|
2923
3012
|
|
|
2924
|
-
blocks.push('CREATE SEQUENCE ' + seq + ' START
|
|
3013
|
+
blocks.push('CREATE SEQUENCE ' + seq + ' START ' + idStart + ';');
|
|
2925
3014
|
blocks.push('CREATE TABLE ' + table + ' (\\n' + columns.join(',\\n') + '\\n);');
|
|
2926
3015
|
if (indexes.length) blocks.push(indexes.join('\\n'));
|
|
2927
3016
|
|
|
@@ -2963,11 +3052,13 @@ function __schema(descriptor) {
|
|
|
2963
3052
|
return def;
|
|
2964
3053
|
}
|
|
2965
3054
|
|
|
2966
|
-
|
|
2967
|
-
globalThis.__ripSchema = {
|
|
3055
|
+
const exports = {
|
|
2968
3056
|
__schema, SchemaError, __SchemaRegistry, __schemaSetAdapter,
|
|
3057
|
+
__version: ${SCHEMA_RUNTIME_ABI_VERSION},
|
|
2969
3058
|
};
|
|
2970
|
-
|
|
3059
|
+
if (typeof globalThis !== 'undefined') globalThis.__ripSchema = exports;
|
|
3060
|
+
return exports;
|
|
3061
|
+
})();
|
|
2971
3062
|
|
|
2972
3063
|
// === End Schema Runtime ===
|
|
2973
3064
|
`;
|
|
@@ -3042,7 +3133,7 @@ export const SCHEMA_INTRINSIC_DECLS = [
|
|
|
3042
3133
|
' first(): Promise<Instance | null>;',
|
|
3043
3134
|
' count(cond?: Record<string, unknown>): Promise<number>;',
|
|
3044
3135
|
' create(data: Partial<Data>): Promise<Instance>;',
|
|
3045
|
-
' toSQL(options?: { dropFirst?: boolean; header?: string }): string;',
|
|
3136
|
+
' toSQL(options?: { dropFirst?: boolean; header?: string; idStart?: number }): string;',
|
|
3046
3137
|
'}',
|
|
3047
3138
|
];
|
|
3048
3139
|
|