tamedevil 0.0.0-0.1

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/dist/index.js ADDED
@@ -0,0 +1,724 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.raw = exports.undefined = exports.tmp = exports.tempVar = exports.te = exports.substring = exports.subcomment = exports.set = exports.run = exports.reservedWords = exports.ref = exports.optionalGet = exports.literal = exports.lit = exports.join = exports.isTE = exports.identifier = exports.get = exports.eval = exports.dangerousKey = exports.compile = exports.canRepresentAsIdentifier = exports.isSafeObjectPropertyName = exports.toJSON = exports.stringifyString = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const lru_1 = tslib_1.__importDefault(require("@graphile/lru"));
6
+ const reservedWords_js_1 = require("./reservedWords.js");
7
+ Object.defineProperty(exports, "reservedWords", { enumerable: true, get: function () { return reservedWords_js_1.reservedWords; } });
8
+ function exportAs(thing, exportName) {
9
+ const existingExport = thing.$$export;
10
+ if (existingExport) {
11
+ if (existingExport.exportName !== exportName) {
12
+ throw new Error(`Attempted to export same thing under multiple names '${existingExport.exportName}' and '${exportName}'`);
13
+ }
14
+ }
15
+ else {
16
+ Object.defineProperty(thing, "$$export", {
17
+ value: { moduleName: "tamedevil", exportName },
18
+ });
19
+ }
20
+ return thing;
21
+ }
22
+ const isDev = process.env.GRAPHILE_ENV === "development";
23
+ /**
24
+ * This is the secret to our safety; since this is a symbol it cannot be faked
25
+ * in a JSON payload and it cannot be constructed with a new Symbol (even with
26
+ * the same argument), so external data cannot make itself trusted.
27
+ */
28
+ const $$type = Symbol("tamedevil-type");
29
+ /**
30
+ * This helps us to avoid GC overhead of allocating new raw nodes all the time
31
+ * when they're likely to be the same values over and over. The average raw
32
+ * string is likely to be around 20 bytes; allowing for 50 bytes once this has
33
+ * been turned into an object, 10000 would mean 500kB which seems an acceptable
34
+ * amount of memory to consume for this.
35
+ */
36
+ const CACHE_RAW_NODES = new lru_1.default({ maxLength: 10000 });
37
+ function makeRawNode(text, exportName) {
38
+ const n = CACHE_RAW_NODES.get(text);
39
+ if (n) {
40
+ return n;
41
+ }
42
+ if (typeof text !== "string") {
43
+ throw new Error(`[tamedevil] Invalid argument to makeRawNode - expected string, but received '${String(text)}'`);
44
+ }
45
+ const newNode = {
46
+ [$$type]: "RAW",
47
+ t: text,
48
+ };
49
+ if (exportName) {
50
+ exportAs(newNode, exportName);
51
+ }
52
+ Object.freeze(newNode);
53
+ CACHE_RAW_NODES.set(text, newNode);
54
+ return newNode;
55
+ }
56
+ // Simple function to help V8 optimize it.
57
+ function makeRefNode(rawValue) {
58
+ return Object.freeze({
59
+ [$$type]: "REF",
60
+ v: rawValue,
61
+ });
62
+ }
63
+ function makeTemporaryVariableNode(symbol) {
64
+ return Object.freeze({
65
+ [$$type]: "VARIABLE",
66
+ s: symbol,
67
+ });
68
+ }
69
+ function makeIndentNode(content) {
70
+ return Object.freeze({
71
+ [$$type]: "INDENT",
72
+ c: content[$$type] === "QUERY" ? content : makeQueryNode([content]),
73
+ });
74
+ }
75
+ function makeQueryNode(nodes) {
76
+ return Object.freeze({
77
+ [$$type]: "QUERY",
78
+ n: nodes,
79
+ });
80
+ }
81
+ function isTE(node) {
82
+ return (typeof node === "object" &&
83
+ node !== null &&
84
+ typeof node[$$type] === "string");
85
+ }
86
+ exports.isTE = isTE;
87
+ function enforceValidNode(node, where) {
88
+ if (isTE(node)) {
89
+ return node;
90
+ }
91
+ throw new Error(`[tamedevil] Invalid expression. Expected an TE item${where ? ` at ${where}` : ""} but received '${String(node)}'. This may mean that there is an issue in the TE expression where a dynamic value was not escaped via 'te.ref(...)', an embedded string wasn't wrapped with 'te.string(...)', or a TE expression was added without using the \`te\` tagged template literal.`);
92
+ }
93
+ /**
94
+ * Accepts an te`...` expression and compiles it out to TE text with
95
+ * placeholders, and the values to substitute for these values.
96
+ */
97
+ function compile(fragment) {
98
+ /**
99
+ * Values hold the JavaScript values that are represented in the query string
100
+ * by placeholders. They are eager because they were provided before compile
101
+ * time.
102
+ */
103
+ const refs = Object.create(null);
104
+ let refCount = 0;
105
+ const refMap = new Map();
106
+ const makeRef = (value) => {
107
+ const existingIdentifier = refMap.get(value);
108
+ if (existingIdentifier) {
109
+ return existingIdentifier;
110
+ }
111
+ refCount++;
112
+ // Arbitrary
113
+ if (refCount > 65535) {
114
+ throw new Error("[tamedevil] This TE statement would contain too many placeholders; tamedevil supports at most 65535 placeholders. To solve this, consider passing multiple values in using a single array or object.");
115
+ }
116
+ const identifier = `_$$_ref_${refCount}`;
117
+ refMap.set(value, identifier);
118
+ refs[identifier] = value;
119
+ return identifier;
120
+ };
121
+ const varMap = new Map();
122
+ let tmpCounter = 0;
123
+ const getVar = (sym) => {
124
+ const existing = varMap.get(sym);
125
+ if (existing) {
126
+ return existing;
127
+ }
128
+ const varName = `_$_tmp${tmpCounter++}`;
129
+ varMap.set(sym, varName);
130
+ return varName;
131
+ };
132
+ function print(untrustedInput, indent = 0) {
133
+ /**
134
+ * Join this to generate the TE query
135
+ */
136
+ const teFragments = [];
137
+ const trustedInput = enforceValidNode(untrustedInput, ``);
138
+ const items = trustedInput[$$type] === "QUERY"
139
+ ? expandQueryNodes(trustedInput)
140
+ : [trustedInput];
141
+ const itemCount = items.length;
142
+ for (let itemIndex = 0; itemIndex < itemCount; itemIndex++) {
143
+ const item = enforceValidNode(items[itemIndex], `item ${itemIndex}`);
144
+ switch (item[$$type]) {
145
+ case "RAW": {
146
+ if (item.t === "") {
147
+ // No need to add blank raw text!
148
+ break;
149
+ }
150
+ // IMPORTANT: this **must not** mangle primitives. Fortunately they're single line so it should be fine.
151
+ teFragments.push(isDev ? item.t.replace(/\n/g, "\n" + " ".repeat(indent)) : item.t);
152
+ break;
153
+ }
154
+ case "REF": {
155
+ const identifier = makeRef(item.v);
156
+ teFragments.push(identifier);
157
+ break;
158
+ }
159
+ case "VARIABLE": {
160
+ const identifier = getVar(item.s);
161
+ teFragments.push(identifier);
162
+ break;
163
+ }
164
+ case "INDENT": {
165
+ if (!isDev) {
166
+ throw new Error("INDENT nodes only allowed in development mode");
167
+ }
168
+ teFragments.push("\n" +
169
+ " ".repeat(indent + 1) +
170
+ print(item.c, indent + 1) +
171
+ "\n" +
172
+ " ".repeat(indent));
173
+ break;
174
+ }
175
+ default: {
176
+ const never = item;
177
+ // This cannot happen
178
+ throw new Error(`Unsupported node found in TE: ${String(never)}`);
179
+ }
180
+ }
181
+ }
182
+ return teFragments.join("");
183
+ }
184
+ let str = print(fragment);
185
+ const variables = [];
186
+ for (const varName of varMap.values()) {
187
+ variables.push(`let ${varName};`);
188
+ }
189
+ if (variables.length > 0) {
190
+ str = variables.join("\n") + "\n" + str;
191
+ }
192
+ const string = isDev ? str.replace(/\n\s*\n/g, "\n") : str;
193
+ return {
194
+ string,
195
+ refs,
196
+ };
197
+ }
198
+ exports.compile = compile;
199
+ // LRU not necessary
200
+ const CACHE_SIMPLE_FRAGMENTS = new Map();
201
+ /**
202
+ * A template string tag that creates a `TE` query out of some strings and
203
+ * some values. Use this to construct all PostgreTE queries to avoid TE
204
+ * injection.
205
+ *
206
+ * Note that using this function, the user *must* specify if they are injecting
207
+ * raw text. This makes a TE injection vulnerability harder to create.
208
+ */
209
+ const teBase = function te(strings, ...values) {
210
+ if (!Array.isArray(strings) || !strings.raw) {
211
+ throw new Error("[tamedevil] te should be used as a template literal, not a function call.");
212
+ }
213
+ const stringsLength = strings.length;
214
+ const first = strings[0];
215
+ // Reduce memory churn with a cache
216
+ if (stringsLength === 1) {
217
+ if (first === "") {
218
+ return blankNode;
219
+ }
220
+ let node = CACHE_SIMPLE_FRAGMENTS.get(first);
221
+ if (!node) {
222
+ node = makeRawNode(first);
223
+ CACHE_SIMPLE_FRAGMENTS.set(first, node);
224
+ }
225
+ return node;
226
+ }
227
+ // Special case te`${...}` - just return the node directly
228
+ if (stringsLength === 2 && strings[0] === "" && strings[1] === "") {
229
+ return values[0];
230
+ }
231
+ const items = [];
232
+ let currentText = "";
233
+ for (let i = 0, l = stringsLength; i < l; i++) {
234
+ const text = strings[i];
235
+ if (typeof text !== "string") {
236
+ throw new Error("[tamedevil] te must be invoked as a template literal, not a function call.");
237
+ }
238
+ currentText += text;
239
+ if (i < l - 1) {
240
+ const rawVal = values[i];
241
+ const valid = enforceValidNode(rawVal, `template literal placeholder ${i}`);
242
+ if (valid[$$type] === "RAW") {
243
+ currentText += valid.t;
244
+ }
245
+ else if (valid[$$type] === "QUERY") {
246
+ const nodes = expandQueryNodes(valid);
247
+ const nodeCount = nodes.length;
248
+ for (let nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++) {
249
+ const node = nodes[nodeIndex];
250
+ if (node[$$type] === "RAW") {
251
+ currentText += node.t;
252
+ }
253
+ else {
254
+ if (currentText !== "") {
255
+ items.push(makeRawNode(currentText));
256
+ currentText = "";
257
+ }
258
+ items.push(node);
259
+ }
260
+ }
261
+ }
262
+ else {
263
+ if (currentText !== "") {
264
+ items.push(makeRawNode(currentText));
265
+ currentText = "";
266
+ }
267
+ items.push(valid);
268
+ }
269
+ }
270
+ }
271
+ if (currentText !== "") {
272
+ items.push(makeRawNode(currentText));
273
+ currentText = "";
274
+ }
275
+ return items.length === 1 ? items[0] : makeQueryNode(items);
276
+ };
277
+ let rawWarningOutput = false;
278
+ /**
279
+ * Creates a TE item for a raw code string. Just plain ol‘ raw code. This
280
+ * method is dangerous though because it involves no escaping, so proceed with
281
+ * caution! It's very very rarely warranted - there is likely a safer way of
282
+ * achieving your goal. DO NOT USE THIS WITH UNTRUSTED INPUT!
283
+ */
284
+ function raw(text) {
285
+ if (!rawWarningOutput) {
286
+ rawWarningOutput = true;
287
+ try {
288
+ throw new Error("te.raw first invoked here");
289
+ }
290
+ catch (e) {
291
+ console.warn(`[tamedevil] WARNING: you're using the te.raw escape hatch, usage of this API is rarely required and is highly discouraged. Please be sure this is what you intend. ${e.stack}`);
292
+ }
293
+ }
294
+ if (typeof text !== "string") {
295
+ throw new Error(`[tamedevil] te.raw must be passed a string, but it was passed '${String(text)}'.`);
296
+ }
297
+ return makeRawNode(text);
298
+ }
299
+ exports.raw = raw;
300
+ /**
301
+ * Creates a TE item for a value that will be included in our final query.
302
+ * This value will be added in a way which avoids TE injection.
303
+ */
304
+ function ref(val) {
305
+ return makeRefNode(val);
306
+ }
307
+ exports.ref = ref;
308
+ const blankNode = makeRawNode(``, "blank");
309
+ const undefinedNode = makeRawNode(`undefined`, "undefined");
310
+ exports.undefined = undefinedNode;
311
+ /**
312
+ * A regexp that matches the first character that might need escaping in a JSON
313
+ * string. ("Might" because we'd rather be safe.)
314
+ *
315
+ * Unsafe:
316
+ * - `\\`
317
+ * - `"`
318
+ * - control characters
319
+ * - surrogates
320
+ */
321
+ // eslint-disable-next-line no-control-regex
322
+ // const forbiddenCharacters = /["\\\u0000-\u001f\ud800-\udfff]/;
323
+ /**
324
+ * A 'short string' has a length less than or equal to this, and can
325
+ * potentially have JSON.stringify skipped on it if it doesn't contain any of
326
+ * the forbiddenCharacters. To prevent the forbiddenCharacters regexp running
327
+ * for a long time, we cap the length of string we test.
328
+ */
329
+ const MAX_SHORT_STRING_LENGTH = 200; // TODO: what should this be?
330
+ const BACKSLASH_CODE = "\\".charCodeAt(0);
331
+ const QUOTE_CODE = '"'.charCodeAt(0);
332
+ // Bizarrely this seems to be faster than the regexp approach
333
+ function stringifyString(value) {
334
+ const l = value.length;
335
+ if (l > MAX_SHORT_STRING_LENGTH) {
336
+ return JSON.stringify(value);
337
+ }
338
+ // Scan through for disallowed charcodes
339
+ for (let i = 0; i < l; i++) {
340
+ const code = value.charCodeAt(i);
341
+ if (code === BACKSLASH_CODE ||
342
+ code === QUOTE_CODE ||
343
+ (code & 0xffe0) === 0 || // equivalent to `code <= 0x001f`
344
+ (code & 0xc000) !== 0 // Not quite equivalent to `code >= 0xd800`, but good enough for our purposes
345
+ ) {
346
+ // Backslash, quote, control character or surrogate
347
+ return JSON.stringify(value);
348
+ }
349
+ }
350
+ return `"${value}"`;
351
+ }
352
+ exports.stringifyString = stringifyString;
353
+ // TODO: more optimal stringifier
354
+ // TODO: rename to jsonStringify?
355
+ const toJSON = (value) => {
356
+ if (value == null)
357
+ return "null";
358
+ if (value === true)
359
+ return "true";
360
+ if (value === false)
361
+ return "false";
362
+ const t = typeof value;
363
+ if (t === "number")
364
+ return "" + value;
365
+ if (t === "string") {
366
+ return stringifyString(value);
367
+ }
368
+ return JSON.stringify(value);
369
+ };
370
+ exports.toJSON = toJSON;
371
+ /**
372
+ * If the value is simple will inline it into the query, otherwise will defer
373
+ * to `te.ref`.
374
+ */
375
+ function lit(val) {
376
+ if (val === undefined) {
377
+ return undefinedNode;
378
+ }
379
+ else if (val === null ||
380
+ typeof val === "string" ||
381
+ typeof val === "boolean" ||
382
+ (typeof val === "number" && Number.isFinite(val))) {
383
+ /*
384
+ * Prior to ECMAScript 2019, JSON wasn't truly a subset of JS - it was possible
385
+ * to encode characters in JSON that JS couldn't parse via `eval` (notably
386
+ * `\u2028` and friends), however as of ES2019 JSON is now a subset of JS, so
387
+ * JSON.stringify is safe.
388
+ *
389
+ * https://github.com/tc39/proposal-json-superset
390
+ */
391
+ const primitive = val;
392
+ return makeRawNode((0, exports.toJSON)(primitive));
393
+ }
394
+ else {
395
+ return ref(val);
396
+ }
397
+ }
398
+ exports.lit = lit;
399
+ exports.literal = lit;
400
+ /**
401
+ * If you're building a string and you want to inject untrusted content into it
402
+ * without opening yourself to code injection attacks, this is the method for
403
+ * you. Example:
404
+ *
405
+ * ```js
406
+ * const code = te`const str = "abc${te.substring(untrusted, '"')}123";`
407
+ * ```
408
+ */
409
+ function substring(text, stringType) {
410
+ // Quick scan to see if it's safe to use verbatim
411
+ const l = text.length;
412
+ if (l < MAX_SHORT_STRING_LENGTH) {
413
+ const stringTypeCode = stringType.charCodeAt(0);
414
+ let verbatim = true;
415
+ for (let i = 0; i < l; i++) {
416
+ const code = text.charCodeAt(i);
417
+ if (code === BACKSLASH_CODE ||
418
+ code === stringTypeCode ||
419
+ (code & 0xffe0) === 0 || // equivalent to `code <= 0x001f`
420
+ (code & 0xc000) !== 0 // Not quite equivalent to `code >= 0xd800`, but good enough for our purposes
421
+ ) {
422
+ // Backslash, quote, control character or surrogate
423
+ verbatim = false;
424
+ break;
425
+ }
426
+ }
427
+ if (verbatim) {
428
+ return makeRawNode(text);
429
+ }
430
+ }
431
+ // Not safe to use verbatim, so let's escape it
432
+ // This'll escape most things that need escaping - backslashes, fancy characters, double quotes, etc.
433
+ const jsonStringified = JSON.stringify(text);
434
+ // But we're already in a string so we don't want the quote marks
435
+ const inner = jsonStringified.substring(1, jsonStringified.length - 1);
436
+ // And if we're not inside a `"` we'll need to escape our string type.
437
+ const escaped = stringType === '"'
438
+ ? inner // "" strings already escapes
439
+ : stringType === "'"
440
+ ? inner.replace(/'/g, "\\'") // '' strings need `'` escaped too (`\` has already been escaped)
441
+ : inner.replace(/[`$]/g, "\\$&"); // `` strings need both '`' and `$` to be escaped
442
+ // Finally return a raw node
443
+ return makeRawNode(escaped);
444
+ }
445
+ exports.substring = substring;
446
+ /**
447
+ * Escapes `content` so that it can be safely embedded in a multiline comment.
448
+ */
449
+ function subcomment(content) {
450
+ return makeRawNode(String(content).replace(/\*\//g, "* /"));
451
+ }
452
+ exports.subcomment = subcomment;
453
+ const disallowedKeys = [
454
+ ...Object.getOwnPropertyNames(Object.prototype),
455
+ ...Object.getOwnPropertySymbols(Object.prototype),
456
+ ];
457
+ /**
458
+ * Is safe to set as the key of a POJO (without a null prototype).
459
+ */
460
+ const isSafeObjectPropertyName = (key) => (typeof key === "number" ||
461
+ typeof key === "symbol" ||
462
+ (typeof key === "string" &&
463
+ /^(?:[0-9a-z$]|_[a-z0-9$])[a-z0-9_$]*$/i.test(key))) &&
464
+ !disallowedKeys.includes(key);
465
+ exports.isSafeObjectPropertyName = isSafeObjectPropertyName;
466
+ /**
467
+ * Can represent as an identifier rather than a string key
468
+ *
469
+ * @remarks
470
+ * Doesn't allow it to start with two underscores.
471
+ */
472
+ const canRepresentAsIdentifier = (key) => Number.isFinite(key) ||
473
+ (typeof key === "string" &&
474
+ (key === "_" || /^(?:[a-z$]|_[a-z0-9$])[a-z0-9_$]*$/i.test(key)));
475
+ exports.canRepresentAsIdentifier = canRepresentAsIdentifier;
476
+ function isValidVariableName(name) {
477
+ if (reservedWords_js_1.reservedWords.has(name)) {
478
+ return false;
479
+ }
480
+ if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name)) {
481
+ return false;
482
+ }
483
+ return true;
484
+ }
485
+ function identifier(name) {
486
+ if (!isValidVariableName(name)) {
487
+ throw new Error(`Invalid identifier name '${name}'`);
488
+ }
489
+ return makeRawNode(name);
490
+ }
491
+ exports.identifier = identifier;
492
+ // TODO: rename to `ensureSafeKey` or `safeKeyOrThrow` or something?
493
+ /**
494
+ * IMPORTANT: It's strongly recommended that instead of defining an object via
495
+ * `const obj = { ${te.dangerousKey(untrustedKey)}: value }` you instead use
496
+ * `const obj = Object.create(null);` and then set the properties on the resulting
497
+ * object via `${obj}[${te.lit(untrustedKey)}] = value;` - this prevents attacks such as
498
+ * **prototype polution** since properties like `__proto__` are not special on
499
+ * null-prototype objects, whereas they can cause havok in regular `{}` objects.
500
+ */
501
+ function dangerousKey(key) {
502
+ if ((0, exports.isSafeObjectPropertyName)(key)) {
503
+ if ((0, exports.canRepresentAsIdentifier)(key)) {
504
+ return makeRawNode(String(key));
505
+ }
506
+ else {
507
+ return makeRawNode(JSON.stringify(key));
508
+ }
509
+ }
510
+ else {
511
+ throw new Error(`Forbidden object key: ${JSON.stringify(key)}; consider using 'Object.create(null)' and assigning properties using te.lit.`);
512
+ }
513
+ }
514
+ exports.dangerousKey = dangerousKey;
515
+ function canAccessViaDot(str) {
516
+ return (str.length < MAX_SHORT_STRING_LENGTH &&
517
+ /^[$_a-zA-Z][$_a-zA-Z0-9]*$/.test(str));
518
+ }
519
+ /**
520
+ * Accesses the key of an object either via `.` or `[]` as appropriate;
521
+ * `obj${te.get(key)}` would become `obj.foo` or `obj["1foo"]` as
522
+ * appropriate.
523
+ */
524
+ function get(key) {
525
+ return typeof key === "string" && canAccessViaDot(key)
526
+ ? // ._mySimpleProperty
527
+ te `.${makeRawNode(key)}`
528
+ : // ["@@meaning"]
529
+ te `[${te.lit(key)}]`;
530
+ }
531
+ exports.get = get;
532
+ /**
533
+ * Accesses the key of an object via optional-chaining:
534
+ * `obj${te.optionalGet(key)}` would become `obj?.foo` or `obj?.["1foo"]` as
535
+ * appropriate.
536
+ */
537
+ function optionalGet(key) {
538
+ return typeof key === "string" && canAccessViaDot(key)
539
+ ? // ?._mySimpleProperty
540
+ te `?.${makeRawNode(key)}`
541
+ : // ?.["@@meaning"]
542
+ te `?.[${te.lit(key)}]`;
543
+ }
544
+ exports.optionalGet = optionalGet;
545
+ // TODO: rename this. 'leftSet'? 'leftAccess'? 'safeAccess'?
546
+ /**
547
+ * Sets the key of an object either via `.` or `[]` as appropriate;
548
+ * `obj${te.set(key)}` would become `obj.foo` or `obj["1foo"]` as
549
+ * appropriate.
550
+ *
551
+ * If the object you're setting properties on has a `null` prototype
552
+ * (`Object.create(null)`) then you can set `hasNullPrototype` to true and all
553
+ * keys are allowed. If this is not the case, then an error will be thrown on
554
+ * certain potentially dangerous keys such as `__proto__` or `constructor`.
555
+ */
556
+ function set(key, hasNullPrototype = false) {
557
+ if (!hasNullPrototype && disallowedKeys.includes(key)) {
558
+ throw new Error(`Attempted to set '${String(key)}' on an object that isn't declared as having a null prototype. This could be unsafe.`);
559
+ }
560
+ return typeof key === "string" && canAccessViaDot(key)
561
+ ? // ._mySimpleProperty
562
+ te `.${makeRawNode(key)}`
563
+ : // ["@@meaning"]
564
+ te `[${te.lit(key)}]`;
565
+ }
566
+ exports.set = set;
567
+ /**
568
+ * @experimental
569
+ */
570
+ function tempVar(symbol = Symbol()) {
571
+ return makeTemporaryVariableNode(symbol);
572
+ }
573
+ exports.tempVar = tempVar;
574
+ function tmp(obj, callback) {
575
+ const varName = te.tempVar();
576
+ return te `(${varName} = ${obj}, ${callback(varName)})`;
577
+ }
578
+ exports.tmp = tmp;
579
+ function run(fragmentOrStrings, ...values) {
580
+ if ("raw" in fragmentOrStrings) {
581
+ return run(te(fragmentOrStrings, ...values));
582
+ }
583
+ if (values.length > 0) {
584
+ throw new Error("Invalid call to `te.run`");
585
+ }
586
+ const fragment = fragmentOrStrings;
587
+ const compiled = compile(fragment);
588
+ const argNames = Object.keys(compiled.refs);
589
+ const argValues = Object.values(compiled.refs);
590
+ try {
591
+ return newFunction(...argNames, compiled.string)(...argValues);
592
+ }
593
+ catch (e) {
594
+ // TODO: improve this!
595
+ console.error(`Error occurred during code generation:`);
596
+ console.error(e);
597
+ console.error("Function definition:");
598
+ console.error(compiled.string);
599
+ throw new Error(`Error occurred during code generation.`);
600
+ }
601
+ }
602
+ exports.eval = run;
603
+ exports.run = run;
604
+ /** Because `new Function` retains the scope, we do it at top level to avoid capturing extra values */
605
+ function newFunction(...args) {
606
+ return new Function(...args);
607
+ }
608
+ /**
609
+ * Join some TE items together, optionally separated by a string. Useful when
610
+ * dealing with lists of TE items, for example a dynamic list of columns or
611
+ * variadic TE function arguments.
612
+ */
613
+ function join(items, separator = "") {
614
+ if (!Array.isArray(items)) {
615
+ throw new Error(`[tamedevil] Invalid te.join call - the first argument should be an array, but it was '${String(items)}'.`);
616
+ }
617
+ if (typeof separator !== "string") {
618
+ throw new Error(`[tamedevil] Invalid separator passed to te.join - must be a string, but we received '${String(separator)}'`);
619
+ }
620
+ // Short circuit joins of size <= 1
621
+ if (items.length === 0) {
622
+ return blankNode;
623
+ }
624
+ else if (items.length === 1) {
625
+ const rawNode = items[0];
626
+ const node = enforceValidNode(rawNode, `join item ${0}`);
627
+ return node;
628
+ }
629
+ const hasSeparator = separator.length > 0;
630
+ let currentText = "";
631
+ const currentItems = [];
632
+ for (let i = 0, l = items.length; i < l; i++) {
633
+ const rawNode = items[i];
634
+ const addSeparator = i > 0 && hasSeparator;
635
+ const node = enforceValidNode(rawNode, `join item ${i}`);
636
+ if (addSeparator) {
637
+ currentText += separator;
638
+ }
639
+ if (node[$$type] === "QUERY") {
640
+ for (const innerNode of expandQueryNodes(node)) {
641
+ if (innerNode[$$type] === "RAW") {
642
+ currentText += innerNode.t;
643
+ }
644
+ else {
645
+ if (currentText !== "") {
646
+ currentItems.push(makeRawNode(currentText));
647
+ currentText = "";
648
+ }
649
+ currentItems.push(innerNode);
650
+ }
651
+ }
652
+ }
653
+ else if (node[$$type] === "RAW") {
654
+ currentText += node.t;
655
+ }
656
+ else {
657
+ if (currentText !== "") {
658
+ currentItems.push(makeRawNode(currentText));
659
+ currentText = "";
660
+ }
661
+ currentItems.push(node);
662
+ }
663
+ }
664
+ if (currentText !== "") {
665
+ currentItems.push(makeRawNode(currentText));
666
+ currentText = "";
667
+ }
668
+ return currentItems.length === 1
669
+ ? currentItems[0]
670
+ : makeQueryNode(currentItems);
671
+ }
672
+ exports.join = join;
673
+ function expandQueryNodes(node) {
674
+ return node.n;
675
+ }
676
+ function indent(fragmentOrStrings, ...values) {
677
+ const fragment = "raw" in fragmentOrStrings
678
+ ? te(fragmentOrStrings, ...values)
679
+ : fragmentOrStrings;
680
+ if (!isDev) {
681
+ return fragment;
682
+ }
683
+ return makeIndentNode(fragment);
684
+ }
685
+ function indentIf(condition, fragment) {
686
+ return isDev && condition ? makeIndentNode(fragment) : fragment;
687
+ }
688
+ const te = teBase;
689
+ exports.te = te;
690
+ exports.default = te;
691
+ const attributes = {
692
+ te,
693
+ ref,
694
+ reference: ref,
695
+ lit,
696
+ literal: lit,
697
+ substring,
698
+ subcomment,
699
+ join,
700
+ identifier,
701
+ dangerousKey,
702
+ get,
703
+ optionalGet,
704
+ set,
705
+ tmp,
706
+ tempVar,
707
+ run,
708
+ eval: run,
709
+ compile,
710
+ indent,
711
+ indentIf,
712
+ undefined: undefinedNode,
713
+ blank: blankNode,
714
+ isTE,
715
+ reservedWords: reservedWords_js_1.reservedWords,
716
+ raw,
717
+ };
718
+ Object.entries(attributes).forEach(([exportName, value]) => {
719
+ if (!value.$$export) {
720
+ exportAs(value, exportName);
721
+ }
722
+ });
723
+ Object.assign(teBase, attributes);
724
+ //# sourceMappingURL=index.js.map